@praxisui/dynamic-form 10.0.0-rc.31 → 10.0.0-rc.33

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.
@@ -20,10 +20,10 @@ import * as i9 from '@angular/material/snack-bar';
20
20
  import { MatSnackBarModule } from '@angular/material/snack-bar';
21
21
  import * as i19 from '@angular/material/badge';
22
22
  import { MatBadgeModule } from '@angular/material/badge';
23
- import { map, Subscription, merge, of, timer, NEVER, EMPTY, defer, TimeoutError, Subject, firstValueFrom, BehaviorSubject, throwError, combineLatest, Observable, race, from, debounceTime as debounceTime$1, takeUntil as takeUntil$1 } from 'rxjs';
23
+ import { map, Subscription, merge, of, timer, NEVER, EMPTY, defer, TimeoutError, Subject, firstValueFrom, BehaviorSubject, throwError, combineLatest, Observable, race, from, debounceTime as debounceTime$1, takeUntil as takeUntil$1, defaultIfEmpty, switchMap as switchMap$1, catchError as catchError$1, take as take$1, finalize as finalize$1 } from 'rxjs';
24
24
  import { filter, map as map$1, switchMap, take, timeout, throwIfEmpty, tap, catchError, ignoreElements, finalize, takeUntil, skip, debounceTime, startWith } from 'rxjs/operators';
25
25
  import * as i1 from '@praxisui/core';
26
- import { serializeEntityLookupValueForPayload, PraxisI18nService, PraxisIconButtonComponent, PraxisIconDirective, providePraxisI18nConfig, RULE_PROPERTY_SCHEMA, FIELD_METADATA_CAPABILITIES, migrateFormLayoutRule, PraxisJsonLogicService, normalizeFormLayoutItems, deepMerge, createDefaultFormConfig, normalizeFormConfig as normalizeFormConfig$1, ensureIds, createEmptyRichContentDocument, formIdentityIssues, resolveHidden, resolveOrder, resolveSpan, projectGroupedCommandPartialRows, isPraxisRuntimeGlobalActionEffect, ASYNC_CONFIG_STORAGE, withFormConfigSections, createFieldLayoutItem, getFormLayoutFieldNames, LoggerService, createCorporateLoggerConfig, ConsoleLoggerSink, ResourceDiscoveryService, GlobalActionService, SCHEMA_METADATA_CLIENT_OPTIONS, ResourceQuickConnectComponent, composeHeadersWithVersion, buildApiUrl, readStrongResourceEtag, ApiEndpoint, mapFieldDefinitionsToMetadata, reconcileFormConfig, applyLocalFormCustomizations, materializeFormLayoutFromMetadata, syncWithServerMetadata, PRAXIS_LOADING_CTX, evaluateFieldAccess, normalizeControlTypeKey, normalizePraxisEffectPolicy, buildPraxisEffectDistinctKey, getFormColumnFieldNames, resolveGroupedCommandPartialRowSpans, resolveOffset, buildSchemaIdStorageKeySegment, buildSchemaId, fetchWithETag, resolveControlTypeAlias, getTextTransformer, MemoryCacheAdapter, LocalStorageCacheAdapter, SchemaMetadataClient, DEFAULT_SCHEMA_REQUEST_TIMEOUT_MS, normalizeReactiveDeterminations, CONNECTION_STORAGE, API_URL, PRAXIS_LOADING_RENDERER, FORM_HOOKS_PRESETS, EmptyStateCardComponent, isValidFormConfig, ComponentMetadataRegistry, FieldControlType, isRequiredGlobalActionPayloadMissing, GLOBAL_ACTION_CATALOG, PRAXIS_GLOBAL_ACTION_CATALOG, getGlobalActionCatalog, SURFACE_OPEN_I18N_NAMESPACE, getGlobalActionUiSchema, getRequiredGlobalActionPayloadKeys, hasMeaningfulGlobalActionPayloadValue, SurfaceOpenActionEditorComponent, SURFACE_OPEN_I18N_CONFIG, IconPickerService, validateGlobalActionRefs, normalizeFormMetadata, DynamicFormService, projectComponentAuthoringManifest } from '@praxisui/core';
26
+ import { serializeEntityLookupValueForPayload, PraxisI18nService, PraxisIconButtonComponent, PraxisIconDirective, providePraxisI18nConfig, RULE_PROPERTY_SCHEMA, FIELD_METADATA_CAPABILITIES, migrateFormLayoutRule, PraxisJsonLogicService, normalizeFormLayoutItems, deepMerge, createDefaultFormConfig, normalizeFormConfig as normalizeFormConfig$1, ensureIds, createEmptyRichContentDocument, formIdentityIssues, resolveHidden, resolveOrder, resolveSpan, projectGroupedCommandPartialRows, isPraxisRuntimeGlobalActionEffect, ASYNC_CONFIG_STORAGE, withFormConfigSections, createFieldLayoutItem, getFormLayoutFieldNames, LoggerService, createCorporateLoggerConfig, ConsoleLoggerSink, ResourceDiscoveryService, GlobalActionService, SCHEMA_METADATA_CLIENT_OPTIONS, ResourceQuickConnectComponent, composeHeadersWithVersion, buildApiUrl, readStrongResourceEtag, ApiEndpoint, mapFieldDefinitionsToMetadata, reconcileFormConfig, applyLocalFormCustomizations, materializeFormLayoutFromMetadata, syncWithServerMetadata, PRAXIS_LOADING_CTX, evaluateFieldAccess, normalizeControlTypeKey, normalizePraxisEffectPolicy, buildPraxisEffectDistinctKey, getFormColumnFieldNames, resolveGroupedCommandPartialRowSpans, resolveOffset, buildSchemaIdStorageKeySegment, buildSchemaId, fetchWithETag, resolveControlTypeAlias, getTextTransformer, MemoryCacheAdapter, LocalStorageCacheAdapter, SchemaMetadataClient, DEFAULT_SCHEMA_REQUEST_TIMEOUT_MS, normalizeReactiveDeterminations, CONNECTION_STORAGE, API_URL, PRAXIS_LOADING_RENDERER, FORM_HOOKS_PRESETS, EmptyStateCardComponent, isValidFormConfig, ComponentMetadataRegistry, FieldControlType, isRequiredGlobalActionPayloadMissing, GLOBAL_ACTION_CATALOG, PRAXIS_GLOBAL_ACTION_CATALOG, getGlobalActionCatalog, SURFACE_OPEN_I18N_NAMESPACE, getGlobalActionUiSchema, getRequiredGlobalActionPayloadKeys, hasMeaningfulGlobalActionPayloadValue, SurfaceOpenActionEditorComponent, SURFACE_OPEN_I18N_CONFIG, IconPickerService, validateGlobalActionRefs, normalizeFormMetadata, GenericCrudService, DynamicFormService, projectComponentAuthoringManifest } from '@praxisui/core';
27
27
  import * as i1$1 from '@praxisui/dynamic-fields';
28
28
  import { getControlTypeCatalog, SelectOptionRegistry, ConfirmDialogComponent, DynamicFieldLoaderDirective } from '@praxisui/dynamic-fields';
29
29
  import { BaseAiAdapter, AgenticAuthoringTurnClientService, PraxisAssistantSessionRegistryService, PraxisAssistantTurnOrchestratorService, createPraxisAssistantViewportLayout, PraxisAiAssistantShellComponent } from '@praxisui/ai';
@@ -12363,6 +12363,9 @@ class PraxisDynamicForm {
12363
12363
  component: ResourceQuickConnectComponent,
12364
12364
  inputs: {
12365
12365
  resourcePath: this.resourcePath,
12366
+ intent: 'crud',
12367
+ endpointKey: this.apiEndpointKey ?? undefined,
12368
+ apiUrlEntry: this.apiUrlEntry ?? undefined,
12366
12369
  },
12367
12370
  },
12368
12371
  });
@@ -13474,6 +13477,16 @@ class PraxisDynamicForm {
13474
13477
  if (this.hasRenderableConfig(hostConfig)) {
13475
13478
  await this.reconcileLoadedConfigWithServerSchema(false);
13476
13479
  }
13480
+ else if (localConfig && this.hasRenderableConfig(localConfig)) {
13481
+ const hostTitle = hostConfig.title;
13482
+ this.config = normalizeFormConfig$1({
13483
+ ...localConfig,
13484
+ ...(hostTitle ? { title: hostTitle } : {}),
13485
+ ...(hostConfig.actions ? { actions: hostConfig.actions } : {}),
13486
+ ...(hostConfig.helpPresentation ? { helpPresentation: hostConfig.helpPresentation } : {}),
13487
+ });
13488
+ await this.reconcileLoadedConfigWithServerSchema();
13489
+ }
13477
13490
  else {
13478
13491
  await this.createDefaultConfig();
13479
13492
  }
@@ -21340,34 +21353,125 @@ class PraxisDynamicForm {
21340
21353
  ? new MemoryCacheAdapter()
21341
21354
  : new LocalStorageCacheAdapter({ ttlMs: 30 * 60 * 1000, maxEntries: 200 });
21342
21355
  const client = new SchemaMetadataClient(adapter, this.schemaTransportOptions);
21343
- const loadLegacySchema = async (reason) => {
21356
+ let discoveryAttempted = false;
21357
+ const loadDiscoverySchema = async (reason) => {
21358
+ discoveryAttempted = true;
21344
21359
  if (this.isDestroyed)
21345
21360
  throw new PraxisDynamicFormDestroyedError();
21346
- try {
21347
- this.warnLog('[PDF] schema:metadata-fallback', reason);
21348
- }
21349
- catch { }
21350
- const legacyGetSchema = this.crud?.getSchema;
21351
- if (typeof legacyGetSchema !== 'function') {
21361
+ const rp = String(this.resourcePath || '').trim();
21362
+ const discoveryOptions = {
21363
+ ...(this.apiEndpointKey ? { endpointKey: this.apiEndpointKey } : {}),
21364
+ ...(this.apiUrlEntry ? { apiUrlEntry: this.apiUrlEntry } : {}),
21365
+ };
21366
+ const canLoadViaDiscovery = !!rp &&
21367
+ typeof this.resourceDiscovery?.getResourceSchemaFields === 'function' &&
21368
+ (typeof this.resourceDiscovery.canResolveSchemas !== 'function' ||
21369
+ this.resourceDiscovery.canResolveSchemas(discoveryOptions));
21370
+ if (!canLoadViaDiscovery) {
21352
21371
  throw reason instanceof Error
21353
21372
  ? reason
21354
21373
  : new Error('Schema metadata unavailable and legacy crud.getSchema() not found.');
21355
21374
  }
21356
- const legacyResult = legacyGetSchema.call(this.crud);
21375
+ const inferredFilter = /(^|[-_])filter($|[-_])/i.test(this.formId || '');
21376
+ const useFilterSchema = this.schemaSource === 'filter' || inferredFilter;
21377
+ const hasDedicatedOptions = !!(discoveryOptions.endpointKey || discoveryOptions.apiUrlEntry);
21378
+ const discoveryCall$ = useFilterSchema && typeof this.resourceDiscovery.getResourceFilterSchemaFields === 'function'
21379
+ ? (hasDedicatedOptions
21380
+ ? this.resourceDiscovery.getResourceFilterSchemaFields(rp, discoveryOptions)
21381
+ : this.resourceDiscovery.getResourceFilterSchemaFields(rp))
21382
+ : (hasDedicatedOptions
21383
+ ? this.resourceDiscovery.getResourceSchemaFields(rp, discoveryOptions)
21384
+ : this.resourceDiscovery.getResourceSchemaFields(rp));
21357
21385
  controller.signal.throwIfAborted();
21358
21386
  const cancelled$ = new Observable(subscriber => {
21359
21387
  const abort = () => subscriber.error(controller.signal.reason ?? new DOMException('Schema read cancelled.', 'AbortError'));
21360
21388
  controller.signal.addEventListener('abort', abort, { once: true });
21361
21389
  return () => controller.signal.removeEventListener('abort', abort);
21362
21390
  });
21363
- let legacyRead$ = race(from(legacyResult), cancelled$).pipe(take(1));
21391
+ let discoveryRead$ = race(from(discoveryCall$), cancelled$).pipe(take(1));
21364
21392
  const timeoutMs = this.schemaTransportOptions.requestTimeoutMs ?? DEFAULT_SCHEMA_REQUEST_TIMEOUT_MS;
21365
21393
  if (timeoutMs > 0)
21366
- legacyRead$ = legacyRead$.pipe(timeout(timeoutMs));
21367
- const legacyDefs = await firstValueFrom(legacyRead$);
21394
+ discoveryRead$ = discoveryRead$.pipe(timeout(timeoutMs));
21395
+ const discoveryDefs = await firstValueFrom(discoveryRead$);
21396
+ controller.signal.throwIfAborted();
21397
+ if (this.isDestroyed)
21398
+ throw new PraxisDynamicFormDestroyedError();
21399
+ if (!Array.isArray(discoveryDefs) || discoveryDefs.length === 0) {
21400
+ throw reason instanceof Error
21401
+ ? reason
21402
+ : new Error('Schema metadata unavailable via ResourceDiscoveryService.');
21403
+ }
21404
+ try {
21405
+ const schemaId = buildSchemaId(schemaContext);
21406
+ this.lastSchemaMeta = { schemaId, context: schemaContext };
21407
+ }
21408
+ catch { }
21409
+ this.schemaCache = discoveryDefs;
21410
+ this.emitLoadingState('schema', 'success', 'Schema carregado.');
21411
+ return discoveryDefs;
21412
+ };
21413
+ const loadLegacySchema = async (reason) => {
21414
+ if (this.isDestroyed)
21415
+ throw new PraxisDynamicFormDestroyedError();
21416
+ try {
21417
+ this.warnLog('[PDF] schema:metadata-fallback', reason);
21418
+ }
21419
+ catch { }
21420
+ const legacyGetSchema = this.crud?.getSchema;
21421
+ if (typeof legacyGetSchema !== 'function') {
21422
+ if (discoveryAttempted) {
21423
+ throw reason instanceof Error
21424
+ ? reason
21425
+ : new Error('Schema metadata unavailable and legacy crud.getSchema() not found.');
21426
+ }
21427
+ return await loadDiscoverySchema(reason);
21428
+ }
21429
+ const isMockedLegacy = !!legacyGetSchema?.and;
21430
+ let legacyDefs;
21431
+ try {
21432
+ const legacyResult = legacyGetSchema.call(this.crud);
21433
+ controller.signal.throwIfAborted();
21434
+ const cancelled$ = new Observable(subscriber => {
21435
+ const abort = () => subscriber.error(controller.signal.reason ?? new DOMException('Schema read cancelled.', 'AbortError'));
21436
+ controller.signal.addEventListener('abort', abort, { once: true });
21437
+ return () => controller.signal.removeEventListener('abort', abort);
21438
+ });
21439
+ let legacyRead$ = race(from(legacyResult), cancelled$).pipe(take(1));
21440
+ const timeoutMs = this.schemaTransportOptions.requestTimeoutMs ?? DEFAULT_SCHEMA_REQUEST_TIMEOUT_MS;
21441
+ if (timeoutMs > 0)
21442
+ legacyRead$ = legacyRead$.pipe(timeout(timeoutMs));
21443
+ legacyDefs = await firstValueFrom(legacyRead$);
21444
+ }
21445
+ catch (legacyError) {
21446
+ if (this.isDestroyed || legacyError instanceof PraxisDynamicFormDestroyedError) {
21447
+ throw new PraxisDynamicFormDestroyedError();
21448
+ }
21449
+ if (this.isSchemaTransportInterruption(legacyError) ||
21450
+ controller.signal.aborted ||
21451
+ isMockedLegacy ||
21452
+ discoveryAttempted) {
21453
+ throw legacyError;
21454
+ }
21455
+ return await loadDiscoverySchema(legacyError);
21456
+ }
21368
21457
  controller.signal.throwIfAborted();
21369
21458
  if (this.isDestroyed)
21370
21459
  throw new PraxisDynamicFormDestroyedError();
21460
+ if (!isMockedLegacy &&
21461
+ !discoveryAttempted &&
21462
+ (!Array.isArray(legacyDefs) || legacyDefs.length === 0)) {
21463
+ try {
21464
+ return await loadDiscoverySchema(reason);
21465
+ }
21466
+ catch (discoveryError) {
21467
+ if (this.isDestroyed || discoveryError instanceof PraxisDynamicFormDestroyedError) {
21468
+ throw new PraxisDynamicFormDestroyedError();
21469
+ }
21470
+ if (this.isSchemaTransportInterruption(discoveryError) || controller.signal.aborted) {
21471
+ throw discoveryError;
21472
+ }
21473
+ }
21474
+ }
21371
21475
  try {
21372
21476
  const schemaId = buildSchemaId(schemaContext);
21373
21477
  this.lastSchemaMeta = { schemaId, context: schemaContext };
@@ -21384,6 +21488,14 @@ class PraxisDynamicForm {
21384
21488
  if (preferLegacySchema) {
21385
21489
  return await loadLegacySchema(new Error('Using legacy schema source (mocked crud.getSchema).'));
21386
21490
  }
21491
+ const rp = String(this.resourcePath || '').trim();
21492
+ const isCatalogResourceKey = !!rp &&
21493
+ !String(this.schemaUrl || '').trim() &&
21494
+ !rp.includes('/') &&
21495
+ rp.includes('.');
21496
+ if (isCatalogResourceKey) {
21497
+ return await loadDiscoverySchema(new Error(`Resolving catalog resourceKey "${rp}" via ResourceDiscoveryService.`));
21498
+ }
21387
21499
  if (!ctx.baseUrl) {
21388
21500
  return await loadLegacySchema(new Error('Missing schema metadata base URL.'));
21389
21501
  }
@@ -33487,6 +33599,14 @@ class PraxisFilterForm {
33487
33599
  config;
33488
33600
  formId;
33489
33601
  resourcePath;
33602
+ /**
33603
+ * Optional logical endpoint key for multi-gateway filter schema resolution.
33604
+ */
33605
+ endpointKey;
33606
+ /**
33607
+ * Optional explicit API URL entry override for filter schema resolution.
33608
+ */
33609
+ apiUrlEntry;
33490
33610
  mode;
33491
33611
  formReady = new EventEmitter();
33492
33612
  valueChange = new EventEmitter();
@@ -33497,26 +33617,153 @@ class PraxisFilterForm {
33497
33617
  form = new FormGroup({});
33498
33618
  valueChangesSub;
33499
33619
  statusChangesSub;
33620
+ schemaSub;
33621
+ hydratedFieldMetadata = [];
33622
+ lastHydratedResourceKey = '';
33500
33623
  columnFieldsCache = new WeakMap();
33501
33624
  singleFieldCache = new WeakMap();
33625
+ crudService = (() => {
33626
+ try {
33627
+ return inject(GenericCrudService, { optional: true });
33628
+ }
33629
+ catch {
33630
+ return null;
33631
+ }
33632
+ })();
33633
+ resourceDiscovery = (() => {
33634
+ try {
33635
+ return inject(ResourceDiscoveryService, { optional: true });
33636
+ }
33637
+ catch {
33638
+ return null;
33639
+ }
33640
+ })();
33641
+ cdr = (() => {
33642
+ try {
33643
+ return inject(ChangeDetectorRef, { optional: true });
33644
+ }
33645
+ catch {
33646
+ return null;
33647
+ }
33648
+ })();
33502
33649
  constructor(dynamicForm) {
33503
33650
  this.dynamicForm = dynamicForm;
33504
33651
  }
33505
33652
  ngOnChanges(changes) {
33653
+ const hasConfiguredFields = Array.isArray(this.config?.fieldMetadata) && this.config.fieldMetadata.length > 0;
33654
+ if (hasConfiguredFields) {
33655
+ this.schemaSub?.unsubscribe();
33656
+ this.hydratedFieldMetadata = [];
33657
+ this.lastHydratedResourceKey = '';
33658
+ }
33506
33659
  if ('config' in changes && this.config?.fieldMetadata) {
33507
33660
  this.buildForm();
33508
33661
  }
33662
+ if (!hasConfiguredFields &&
33663
+ ('config' in changes ||
33664
+ 'resourcePath' in changes ||
33665
+ 'endpointKey' in changes ||
33666
+ 'apiUrlEntry' in changes)) {
33667
+ this.loadFilterSchemaFieldsIfNeeded();
33668
+ }
33509
33669
  }
33510
33670
  ngOnDestroy() {
33671
+ this.schemaSub?.unsubscribe();
33511
33672
  this.valueChangesSub?.unsubscribe();
33512
33673
  this.statusChangesSub?.unsubscribe();
33513
33674
  }
33675
+ loadFilterSchemaFieldsIfNeeded() {
33676
+ const rp = String(this.resourcePath ||
33677
+ (this.config?.resourcePath ?? '')).trim();
33678
+ if (!rp) {
33679
+ this.schemaSub?.unsubscribe();
33680
+ if (this.hydratedFieldMetadata.length > 0) {
33681
+ this.hydratedFieldMetadata = [];
33682
+ this.lastHydratedResourceKey = '';
33683
+ this.buildForm();
33684
+ }
33685
+ return;
33686
+ }
33687
+ const discoveryOptions = this.endpointKey || this.apiUrlEntry
33688
+ ? {
33689
+ ...(this.endpointKey ? { endpointKey: this.endpointKey } : {}),
33690
+ ...(this.apiUrlEntry ? { apiUrlEntry: this.apiUrlEntry } : {}),
33691
+ }
33692
+ : undefined;
33693
+ const hydrationKey = JSON.stringify({
33694
+ rp,
33695
+ endpointKey: this.endpointKey ?? null,
33696
+ apiUrlEntry: this.apiUrlEntry ?? null,
33697
+ });
33698
+ if (this.lastHydratedResourceKey === hydrationKey && this.hydratedFieldMetadata.length > 0) {
33699
+ return;
33700
+ }
33701
+ const canLoadViaDiscovery = typeof this.resourceDiscovery?.getResourceFilterSchemaFields === 'function' &&
33702
+ (typeof this.resourceDiscovery.canResolveSchemas !== 'function' ||
33703
+ this.resourceDiscovery.canResolveSchemas(discoveryOptions));
33704
+ const canLoadViaCrud = typeof this.crudService?.getFilterSchema === 'function' ||
33705
+ typeof this.crudService?.getSchema === 'function';
33706
+ if (!canLoadViaCrud && !canLoadViaDiscovery) {
33707
+ return;
33708
+ }
33709
+ const loadFromDiscovery = () => discoveryOptions
33710
+ ? this.resourceDiscovery.getResourceFilterSchemaFields(rp, discoveryOptions)
33711
+ : this.resourceDiscovery.getResourceFilterSchemaFields(rp);
33712
+ const loadFromCrud = () => {
33713
+ if (!canLoadViaCrud || !this.crudService) {
33714
+ return of([]);
33715
+ }
33716
+ if (discoveryOptions) {
33717
+ this.crudService.configure?.(rp, discoveryOptions);
33718
+ }
33719
+ else {
33720
+ this.crudService.configure?.(rp);
33721
+ }
33722
+ return typeof this.crudService.getFilterSchema === 'function'
33723
+ ? this.crudService.getFilterSchema()
33724
+ : this.crudService.getSchema();
33725
+ };
33726
+ const isCatalogResourceKey = !rp.includes('/') && rp.includes('.');
33727
+ let source$;
33728
+ if (isCatalogResourceKey && canLoadViaDiscovery) {
33729
+ source$ = loadFromDiscovery().pipe(defaultIfEmpty([]), switchMap$1((defs) => Array.isArray(defs) && defs.length === 0 && canLoadViaCrud
33730
+ ? loadFromCrud()
33731
+ : of(defs)), catchError$1(() => (canLoadViaCrud ? loadFromCrud() : of([]))));
33732
+ }
33733
+ else if (canLoadViaCrud) {
33734
+ source$ = loadFromCrud().pipe(defaultIfEmpty([]), switchMap$1((defs) => Array.isArray(defs) && defs.length === 0 && canLoadViaDiscovery
33735
+ ? loadFromDiscovery()
33736
+ : of(defs)), catchError$1(() => (canLoadViaDiscovery ? loadFromDiscovery() : of([]))));
33737
+ }
33738
+ else {
33739
+ source$ = loadFromDiscovery();
33740
+ }
33741
+ this.schemaSub?.unsubscribe();
33742
+ this.schemaSub = source$
33743
+ .pipe(take$1(1), catchError$1(() => of([])))
33744
+ .subscribe((defs) => {
33745
+ if (!Array.isArray(defs) || defs.length === 0) {
33746
+ return;
33747
+ }
33748
+ if (Array.isArray(this.config?.fieldMetadata) && this.config.fieldMetadata.length > 0) {
33749
+ return;
33750
+ }
33751
+ const metadata = mapFieldDefinitionsToMetadata(defs);
33752
+ if (!metadata.length) {
33753
+ return;
33754
+ }
33755
+ this.hydratedFieldMetadata = metadata;
33756
+ this.lastHydratedResourceKey = hydrationKey;
33757
+ this.buildForm();
33758
+ this.cdr?.markForCheck();
33759
+ });
33760
+ }
33514
33761
  buildForm() {
33515
33762
  this.valueChangesSub?.unsubscribe();
33516
33763
  this.statusChangesSub?.unsubscribe();
33517
33764
  this.columnFieldsCache = new WeakMap();
33518
33765
  this.singleFieldCache = new WeakMap();
33519
- const fields = this.config.fieldMetadata || [];
33766
+ const fields = this.fieldMetadata;
33520
33767
  const previous = this.form.getRawValue?.() || {};
33521
33768
  this.form = this.dynamicForm.createFormGroupFromMetadata(fields);
33522
33769
  // Preserve previous values only for fields that did not declare a new explicit seed.
@@ -33576,7 +33823,9 @@ class PraxisFilterForm {
33576
33823
  return className || null;
33577
33824
  }
33578
33825
  get fieldMetadata() {
33579
- return this.config?.fieldMetadata || [];
33826
+ return this.config?.fieldMetadata?.length
33827
+ ? this.config.fieldMetadata
33828
+ : this.hydratedFieldMetadata;
33580
33829
  }
33581
33830
  get hasLayout() {
33582
33831
  return (Array.isArray(this.config?.sections) && this.config.sections.length > 0);
@@ -33592,7 +33841,7 @@ class PraxisFilterForm {
33592
33841
  return `col-${i}-${col.fields.join(',')}`;
33593
33842
  }
33594
33843
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisFilterForm, deps: [{ token: i1.DynamicFormService }], target: i0.ɵɵFactoryTarget.Component });
33595
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: PraxisFilterForm, isStandalone: true, selector: "praxis-filter-form", inputs: { config: "config", formId: "formId", resourcePath: "resourcePath", mode: "mode" }, outputs: { formReady: "formReady", valueChange: "valueChange", submit: "submit", requestSearch: "requestSearch", validityChange: "validityChange" }, usesOnChanges: true, ngImport: i0, template: "<form\n [formGroup]=\"form\"\n class=\"praxis-filter-form\"\n (change)=\"$event.stopPropagation()\"\n (ngSubmit)=\"onFormSubmit(); $event.preventDefault(); $event.stopPropagation()\"\n>\n @if (hasLayout) { @for (section of (config.sections || []); track section?.id ??\n $index) {\n <div class=\"filter-section\">\n @if (section.title) {\n <h3>{{ section.title }}</h3>\n } @for (row of section.rows; track $index) {\n <div class=\"filter-row\">\n @for ( column of row.columns; track (column?.fields?.join(',') ?? $index)\n ) {\n <div class=\"filter-column\" [ngClass]=\"getColumnClasses(column)\">\n <ng-container\n dynamicFieldLoader\n [fields]=\"getColumnFields(column)\"\n [formGroup]=\"form\"\n [enableExternalControlBinding]=\"true\"\n ></ng-container>\n </div>\n }\n </div>\n }\n </div>\n } } @else {\n <div class=\"filter-row\">\n @for (field of fieldMetadata; track field?.name ?? $index) {\n <div class=\"filter-column\">\n <ng-container\n dynamicFieldLoader\n [fields]=\"getSingleField(field)\"\n [formGroup]=\"form\"\n [enableExternalControlBinding]=\"true\"\n ></ng-container>\n </div>\n }\n </div>\n }\n <!-- Bot\u00E3o de submit invis\u00EDvel para garantir submit ao pressionar Enter -->\n <button\n type=\"submit\"\n class=\"hidden-submit\"\n aria-hidden=\"true\"\n tabindex=\"-1\"\n ></button>\n</form>\n", styles: [":host{display:block}.praxis-filter-form{padding-top:6px}.filter-row{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:12px 16px}.filter-column{min-width:0}.filter-column--full{grid-column:1/-1}.hidden-submit{display:none;width:0;height:0;padding:0;border:0}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$3.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$3.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$3.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: DynamicFieldLoaderDirective, selector: "[dynamicFieldLoader]", inputs: ["fields", "formGroup", "enableExternalControlBinding", "itemTemplate", "debugTrace", "debugTraceLabel", "readonlyMode", "disabledMode", "presentationMode", "visible", "canvasMode"], outputs: ["componentsCreated", "fieldCreated", "fieldDestroyed", "renderError", "canvasMouseEnter", "canvasMouseLeave", "canvasClick", "canvasFocus"] }] });
33844
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: PraxisFilterForm, isStandalone: true, selector: "praxis-filter-form", inputs: { config: "config", formId: "formId", resourcePath: "resourcePath", endpointKey: "endpointKey", apiUrlEntry: "apiUrlEntry", mode: "mode" }, outputs: { formReady: "formReady", valueChange: "valueChange", submit: "submit", requestSearch: "requestSearch", validityChange: "validityChange" }, usesOnChanges: true, ngImport: i0, template: "<form\n [formGroup]=\"form\"\n class=\"praxis-filter-form\"\n (change)=\"$event.stopPropagation()\"\n (ngSubmit)=\"onFormSubmit(); $event.preventDefault(); $event.stopPropagation()\"\n>\n @if (hasLayout) { @for (section of (config.sections || []); track section?.id ??\n $index) {\n <div class=\"filter-section\">\n @if (section.title) {\n <h3>{{ section.title }}</h3>\n } @for (row of section.rows; track $index) {\n <div class=\"filter-row\">\n @for ( column of row.columns; track (column?.fields?.join(',') ?? $index)\n ) {\n <div class=\"filter-column\" [ngClass]=\"getColumnClasses(column)\">\n <ng-container\n dynamicFieldLoader\n [fields]=\"getColumnFields(column)\"\n [formGroup]=\"form\"\n [enableExternalControlBinding]=\"true\"\n ></ng-container>\n </div>\n }\n </div>\n }\n </div>\n } } @else {\n <div class=\"filter-row\">\n @for (field of fieldMetadata; track field?.name ?? $index) {\n <div class=\"filter-column\">\n <ng-container\n dynamicFieldLoader\n [fields]=\"getSingleField(field)\"\n [formGroup]=\"form\"\n [enableExternalControlBinding]=\"true\"\n ></ng-container>\n </div>\n }\n </div>\n }\n <!-- Bot\u00E3o de submit invis\u00EDvel para garantir submit ao pressionar Enter -->\n <button\n type=\"submit\"\n class=\"hidden-submit\"\n aria-hidden=\"true\"\n tabindex=\"-1\"\n ></button>\n</form>\n", styles: [":host{display:block}.praxis-filter-form{padding-top:6px}.filter-row{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:12px 16px}.filter-column{min-width:0}.filter-column--full{grid-column:1/-1}.hidden-submit{display:none;width:0;height:0;padding:0;border:0}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$3.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$3.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$3.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: DynamicFieldLoaderDirective, selector: "[dynamicFieldLoader]", inputs: ["fields", "formGroup", "enableExternalControlBinding", "itemTemplate", "debugTrace", "debugTraceLabel", "readonlyMode", "disabledMode", "presentationMode", "visible", "canvasMode"], outputs: ["componentsCreated", "fieldCreated", "fieldDestroyed", "renderError", "canvasMouseEnter", "canvasMouseLeave", "canvasClick", "canvasFocus"] }] });
33596
33845
  }
33597
33846
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisFilterForm, decorators: [{
33598
33847
  type: Component,
@@ -33604,6 +33853,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
33604
33853
  type: Input
33605
33854
  }], resourcePath: [{
33606
33855
  type: Input
33856
+ }], endpointKey: [{
33857
+ type: Input
33858
+ }], apiUrlEntry: [{
33859
+ type: Input
33607
33860
  }], mode: [{
33608
33861
  type: Input
33609
33862
  }], formReady: [{
@@ -33709,7 +33962,19 @@ const PRAXIS_FILTER_FORM_COMPONENT_METADATA = {
33709
33962
  name: 'resourcePath',
33710
33963
  type: 'string',
33711
33964
  label: 'Resource',
33712
- description: 'Resource path used by filter schema.',
33965
+ description: 'Resource path or catalog resourceKey used to resolve the filter schema.',
33966
+ },
33967
+ {
33968
+ name: 'endpointKey',
33969
+ type: 'ApiEndpoint',
33970
+ label: 'Endpoint Key',
33971
+ description: 'Canonical endpoint key used to resolve the filter schema in multi-endpoint hosts.',
33972
+ },
33973
+ {
33974
+ name: 'apiUrlEntry',
33975
+ type: 'ApiUrlEntry | null',
33976
+ label: 'API URL Entry',
33977
+ description: 'Explicit API URL entry for detached hosts that render the filter form outside the route injector.',
33713
33978
  },
33714
33979
  {
33715
33980
  name: 'mode',
@@ -33767,18 +34032,89 @@ class PraxisFilterFormWidgetConfigEditor {
33767
34032
  isDirty$ = new BehaviorSubject(false);
33768
34033
  isValid$ = new BehaviorSubject(true);
33769
34034
  isBusy$ = new BehaviorSubject(false);
34035
+ schemaWarning = '';
33770
34036
  subscription = new Subscription();
34037
+ schemaSubscription;
34038
+ childBusy = false;
34039
+ schemaLoading = false;
34040
+ resourcePathDraft;
34041
+ crudService = (() => {
34042
+ try {
34043
+ return inject(GenericCrudService, { optional: true });
34044
+ }
34045
+ catch {
34046
+ return null;
34047
+ }
34048
+ })();
34049
+ resourceDiscovery = (() => {
34050
+ try {
34051
+ return inject(ResourceDiscoveryService, { optional: true });
34052
+ }
34053
+ catch {
34054
+ return null;
34055
+ }
34056
+ })();
34057
+ cdr = (() => {
34058
+ try {
34059
+ return inject(ChangeDetectorRef, { optional: true });
34060
+ }
34061
+ catch {
34062
+ return null;
34063
+ }
34064
+ })();
34065
+ get effectiveResourcePath() {
34066
+ if (this.resourcePathDraft !== undefined) {
34067
+ return this.resourcePathDraft;
34068
+ }
34069
+ return String(this.inputs?.resourcePath || '').trim();
34070
+ }
34071
+ get effectiveEndpointKey() {
34072
+ return this.buildDiscoveryOptions()?.endpointKey;
34073
+ }
34074
+ get effectiveApiUrlEntry() {
34075
+ return this.buildDiscoveryOptions()?.apiUrlEntry;
34076
+ }
34077
+ onResourceQuickConnect(resourcePath) {
34078
+ const normalized = String(resourcePath ?? '').trim();
34079
+ if (!normalized) {
34080
+ this.onResourceQuickClear();
34081
+ return;
34082
+ }
34083
+ this.resourcePathDraft = normalized;
34084
+ this.updateDirtyState(this.formEditor?.isDirty$.value ?? false);
34085
+ if (this.formEditor) {
34086
+ const currentDoc = parseLegacyOrDynamicFormDocument(this.formEditor.getSettingsValue?.() ?? this.createDocumentFromInputs());
34087
+ this.loadFilterSchemaFieldsIfNeeded(normalized, currentDoc, true);
34088
+ }
34089
+ }
34090
+ onResourceQuickClear() {
34091
+ this.schemaSubscription?.unsubscribe();
34092
+ this.schemaLoading = false;
34093
+ this.publishBusy();
34094
+ this.resourcePathDraft = '';
34095
+ this.schemaWarning = '';
34096
+ this.updateDirtyState(this.formEditor?.isDirty$.value ?? false);
34097
+ }
34098
+ updateDirtyState(editorDirty) {
34099
+ const baseline = String(this.inputs?.resourcePath || '').trim();
34100
+ const resourceChanged = this.resourcePathDraft !== undefined && this.resourcePathDraft !== baseline;
34101
+ this.isDirty$.next(editorDirty || resourceChanged);
34102
+ }
33771
34103
  ngAfterViewInit() {
33772
34104
  if (!this.formEditor) {
33773
34105
  return;
33774
34106
  }
33775
34107
  this.formEditor.supportsPresentationSettings = false;
33776
- this.initializeChildEditor();
33777
- this.subscription.add(this.formEditor.isDirty$.subscribe((value) => this.isDirty$.next(value)));
34108
+ this.subscription.add(this.formEditor.isDirty$.subscribe((value) => this.updateDirtyState(value)));
33778
34109
  this.subscription.add(this.formEditor.isValid$.subscribe((value) => this.isValid$.next(value)));
33779
- this.subscription.add(this.formEditor.isBusy$.subscribe((value) => this.isBusy$.next(value)));
34110
+ this.subscription.add(this.formEditor.isBusy$.subscribe((value) => {
34111
+ this.childBusy = value;
34112
+ this.publishBusy();
34113
+ }));
34114
+ this.initializeChildEditor();
33780
34115
  }
33781
34116
  ngOnDestroy() {
34117
+ this.schemaSubscription?.unsubscribe();
33782
34118
  this.subscription.unsubscribe();
33783
34119
  }
33784
34120
  getSettingsValue() {
@@ -33788,27 +34124,146 @@ class PraxisFilterFormWidgetConfigEditor {
33788
34124
  return this.buildValue(this.formEditor?.onSave?.() ?? this.formEditor?.getSettingsValue());
33789
34125
  }
33790
34126
  reset() {
34127
+ this.schemaSubscription?.unsubscribe();
34128
+ this.schemaLoading = false;
34129
+ this.resourcePathDraft = undefined;
34130
+ this.publishBusy();
33791
34131
  this.formEditor?.reset?.();
34132
+ this.updateDirtyState(this.formEditor?.isDirty$.value ?? false);
34133
+ }
34134
+ publishBusy() {
34135
+ this.isBusy$.next(this.childBusy || this.schemaLoading);
33792
34136
  }
33793
34137
  initializeChildEditor() {
33794
34138
  const document = this.createDocumentFromInputs();
34139
+ this.applyBaselineDocumentToChildEditor(document, 'filter-form-widget-config-init');
34140
+ const resourcePath = this.effectiveResourcePath;
34141
+ if (resourcePath &&
34142
+ (!Array.isArray(document.config.fieldMetadata) || document.config.fieldMetadata.length === 0)) {
34143
+ this.loadFilterSchemaFieldsIfNeeded(resourcePath, document);
34144
+ }
34145
+ }
34146
+ applyBaselineDocumentToChildEditor(document, reason) {
33795
34147
  const serialized = serializeDynamicFormAuthoringDocument(document);
33796
34148
  const editor = this.formEditor;
33797
34149
  editor.onJsonConfigChange?.(serialized);
33798
34150
  editor.initialDocument = document;
33799
34151
  editor.initialConfig = document.config;
33800
- editor.updateDirtyState?.('filter-form-widget-config-init');
34152
+ editor.updateDirtyState?.(reason);
33801
34153
  editor.isDirty$?.next(false);
33802
34154
  editor.isValid$?.next(true);
33803
34155
  }
34156
+ loadFilterSchemaFieldsIfNeeded(resourcePath, baselineDocument, forceReload = false) {
34157
+ const discoveryOptions = this.buildDiscoveryOptions();
34158
+ const canLoadViaDiscovery = typeof this.resourceDiscovery?.getResourceFilterSchemaFields === 'function' &&
34159
+ (typeof this.resourceDiscovery.canResolveSchemas !== 'function' ||
34160
+ this.resourceDiscovery.canResolveSchemas(discoveryOptions));
34161
+ const canLoadViaCrud = typeof this.crudService?.getFilterSchema === 'function' ||
34162
+ typeof this.crudService?.getSchema === 'function';
34163
+ if (!canLoadViaCrud && !canLoadViaDiscovery) {
34164
+ return;
34165
+ }
34166
+ const loadFromDiscovery = () => discoveryOptions
34167
+ ? this.resourceDiscovery.getResourceFilterSchemaFields(resourcePath, discoveryOptions)
34168
+ : this.resourceDiscovery.getResourceFilterSchemaFields(resourcePath);
34169
+ const loadFromCrud = () => {
34170
+ if (!canLoadViaCrud || !this.crudService) {
34171
+ return of([]);
34172
+ }
34173
+ if (discoveryOptions?.endpointKey || discoveryOptions?.apiUrlEntry) {
34174
+ this.crudService.configure?.(resourcePath, {
34175
+ ...(discoveryOptions.endpointKey ? { endpointKey: discoveryOptions.endpointKey } : {}),
34176
+ ...(discoveryOptions.apiUrlEntry ? { apiUrlEntry: discoveryOptions.apiUrlEntry } : {}),
34177
+ });
34178
+ }
34179
+ else {
34180
+ this.crudService.configure?.(resourcePath);
34181
+ }
34182
+ return typeof this.crudService.getFilterSchema === 'function'
34183
+ ? this.crudService.getFilterSchema()
34184
+ : this.crudService.getSchema();
34185
+ };
34186
+ const isCatalogResourceKey = !resourcePath.includes('/') && resourcePath.includes('.');
34187
+ let source$;
34188
+ if (isCatalogResourceKey && canLoadViaDiscovery) {
34189
+ source$ = loadFromDiscovery().pipe(defaultIfEmpty([]), switchMap$1((defs) => Array.isArray(defs) && defs.length === 0 && canLoadViaCrud
34190
+ ? loadFromCrud()
34191
+ : of(defs)), catchError$1(() => (canLoadViaCrud ? loadFromCrud() : of([]))));
34192
+ }
34193
+ else if (canLoadViaCrud) {
34194
+ source$ = loadFromCrud().pipe(defaultIfEmpty([]), switchMap$1((defs) => Array.isArray(defs) && defs.length === 0 && canLoadViaDiscovery
34195
+ ? loadFromDiscovery()
34196
+ : of(defs)), catchError$1(() => (canLoadViaDiscovery ? loadFromDiscovery() : of([]))));
34197
+ }
34198
+ else {
34199
+ source$ = loadFromDiscovery();
34200
+ }
34201
+ this.schemaSubscription?.unsubscribe();
34202
+ this.schemaWarning = '';
34203
+ this.schemaLoading = true;
34204
+ this.publishBusy();
34205
+ this.schemaSubscription = source$
34206
+ .pipe(take$1(1), catchError$1(() => of([])), finalize$1(() => {
34207
+ this.schemaLoading = false;
34208
+ this.publishBusy();
34209
+ this.cdr?.markForCheck();
34210
+ }))
34211
+ .subscribe((defs) => {
34212
+ if (!Array.isArray(defs) || defs.length === 0) {
34213
+ this.schemaWarning = `No filter schema fields were returned for resource "${resourcePath}".`;
34214
+ this.cdr?.markForCheck();
34215
+ return;
34216
+ }
34217
+ if ((!forceReload && this.isDirty$.value) || !this.formEditor) {
34218
+ return;
34219
+ }
34220
+ const fieldMetadata = mapFieldDefinitionsToMetadata(defs);
34221
+ if (!fieldMetadata.length) {
34222
+ this.schemaWarning = `No filter schema fields were returned for resource "${resourcePath}".`;
34223
+ this.cdr?.markForCheck();
34224
+ return;
34225
+ }
34226
+ const newFieldNames = new Set(fieldMetadata.map((f) => f.name));
34227
+ const existingSectionFields = (baselineDocument.config.sections ?? []).flatMap((section) => (section.rows ?? []).flatMap((row) => (row.columns ?? []).flatMap((col) => (col.fields ?? []).map((f) => typeof f === 'string' ? f : f?.name ?? '')))).filter(Boolean);
34228
+ const shouldResetSections = forceReload &&
34229
+ existingSectionFields.length > 0 &&
34230
+ !existingSectionFields.some((fieldName) => newFieldNames.has(fieldName));
34231
+ const nextDocument = {
34232
+ ...baselineDocument,
34233
+ config: {
34234
+ ...baselineDocument.config,
34235
+ ...(shouldResetSections ? { sections: [] } : {}),
34236
+ fieldMetadata,
34237
+ },
34238
+ };
34239
+ this.applyBaselineDocumentToChildEditor(nextDocument, 'filter-form-widget-config-schema');
34240
+ this.updateDirtyState(this.formEditor.isDirty$.value);
34241
+ this.cdr?.markForCheck();
34242
+ });
34243
+ }
34244
+ buildDiscoveryOptions() {
34245
+ const rawEndpointKey = this.inputs?.apiEndpointKey ?? this.inputs?.endpointKey;
34246
+ const endpointKey = String(rawEndpointKey ?? '').trim();
34247
+ const apiUrlEntry = this.inputs?.apiUrlEntry;
34248
+ if (!endpointKey && !apiUrlEntry) {
34249
+ return undefined;
34250
+ }
34251
+ return {
34252
+ ...(endpointKey ? { endpointKey } : {}),
34253
+ ...(apiUrlEntry ? { apiUrlEntry } : {}),
34254
+ };
34255
+ }
33804
34256
  buildValue(rawDocument) {
33805
34257
  const document = parseLegacyOrDynamicFormDocument(rawDocument ?? this.createDocumentFromInputs());
34258
+ const resolvedResourcePath = this.resourcePathDraft !== undefined
34259
+ ? this.resourcePathDraft || undefined
34260
+ : this.inputs?.resourcePath;
33806
34261
  return {
33807
34262
  inputs: {
33808
34263
  ...(this.inputs ?? {}),
33809
34264
  config: document.config,
33810
34265
  formId: this.inputs?.formId ?? this.widgetKey,
33811
- resourcePath: this.inputs?.resourcePath,
34266
+ resourcePath: resolvedResourcePath,
33812
34267
  mode: document.bindings?.mode ?? this.inputs?.mode ?? 'create',
33813
34268
  },
33814
34269
  };
@@ -33829,25 +34284,47 @@ class PraxisFilterFormWidgetConfigEditor {
33829
34284
  : undefined;
33830
34285
  }
33831
34286
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisFilterFormWidgetConfigEditor, deps: [], target: i0.ɵɵFactoryTarget.Component });
33832
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.14", type: PraxisFilterFormWidgetConfigEditor, isStandalone: true, selector: "praxis-filter-form-widget-config-editor", inputs: { inputs: "inputs", widgetKey: "widgetKey" }, viewQueries: [{ propertyName: "formEditor", first: true, predicate: ["formEditor"], descendants: true }], ngImport: i0, template: `
34287
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: PraxisFilterFormWidgetConfigEditor, isStandalone: true, selector: "praxis-filter-form-widget-config-editor", inputs: { inputs: "inputs", widgetKey: "widgetKey" }, providers: [{ provide: SETTINGS_PANEL_DATA, useValue: null }], viewQueries: [{ propertyName: "formEditor", first: true, predicate: ["formEditor"], descendants: true }], ngImport: i0, template: `
33833
34288
  <section data-testid="filter-form-widget-config-editor">
34289
+ <div class="filter-form-resource-quick-connect" data-testid="filter-form-resource-quick-connect">
34290
+ <praxis-resource-quick-connect
34291
+ [resourcePath]="effectiveResourcePath"
34292
+ [endpointKey]="effectiveEndpointKey"
34293
+ [apiUrlEntry]="effectiveApiUrlEntry"
34294
+ intent="filter"
34295
+ (resourcePathChange)="onResourceQuickConnect($event)"
34296
+ ></praxis-resource-quick-connect>
34297
+ </div>
34298
+ @if (schemaWarning) {
34299
+ <p class="filter-form-editor-schema-warning" data-testid="filter-form-schema-warning" role="status">
34300
+ {{ schemaWarning }}
34301
+ </p>
34302
+ }
33834
34303
  <praxis-dynamic-form-config-editor #formEditor />
33835
34304
  </section>
33836
- `, isInline: true, dependencies: [{ kind: "component", type: PraxisDynamicFormConfigEditor, selector: "praxis-dynamic-form-config-editor" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
34305
+ `, isInline: true, styles: [".filter-form-resource-quick-connect{padding:var(--p-space-4, 16px) var(--p-space-4, 16px) var(--p-space-2, 8px)}\n"], dependencies: [{ kind: "component", type: PraxisDynamicFormConfigEditor, selector: "praxis-dynamic-form-config-editor" }, { kind: "component", type: ResourceQuickConnectComponent, selector: "praxis-resource-quick-connect", inputs: ["resourcePath", "endpointKey", "apiUrlEntry", "intent", "resources", "label", "placeholder", "hint", "disabled"], outputs: ["resourcePathChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
33837
34306
  }
33838
34307
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisFilterFormWidgetConfigEditor, decorators: [{
33839
34308
  type: Component,
33840
- args: [{
33841
- selector: 'praxis-filter-form-widget-config-editor',
33842
- standalone: true,
33843
- imports: [PraxisDynamicFormConfigEditor],
33844
- template: `
34309
+ args: [{ providers: [{ provide: SETTINGS_PANEL_DATA, useValue: null }], selector: 'praxis-filter-form-widget-config-editor', standalone: true, imports: [PraxisDynamicFormConfigEditor, ResourceQuickConnectComponent], template: `
33845
34310
  <section data-testid="filter-form-widget-config-editor">
34311
+ <div class="filter-form-resource-quick-connect" data-testid="filter-form-resource-quick-connect">
34312
+ <praxis-resource-quick-connect
34313
+ [resourcePath]="effectiveResourcePath"
34314
+ [endpointKey]="effectiveEndpointKey"
34315
+ [apiUrlEntry]="effectiveApiUrlEntry"
34316
+ intent="filter"
34317
+ (resourcePathChange)="onResourceQuickConnect($event)"
34318
+ ></praxis-resource-quick-connect>
34319
+ </div>
34320
+ @if (schemaWarning) {
34321
+ <p class="filter-form-editor-schema-warning" data-testid="filter-form-schema-warning" role="status">
34322
+ {{ schemaWarning }}
34323
+ </p>
34324
+ }
33846
34325
  <praxis-dynamic-form-config-editor #formEditor />
33847
34326
  </section>
33848
- `,
33849
- changeDetection: ChangeDetectionStrategy.OnPush,
33850
- }]
34327
+ `, changeDetection: ChangeDetectionStrategy.OnPush, styles: [".filter-form-resource-quick-connect{padding:var(--p-space-4, 16px) var(--p-space-4, 16px) var(--p-space-2, 8px)}\n"] }]
33851
34328
  }], propDecorators: { inputs: [{
33852
34329
  type: Input
33853
34330
  }], widgetKey: [{
@@ -33863,40 +34340,97 @@ var praxisFilterFormWidgetConfigEditor = /*#__PURE__*/Object.freeze({
33863
34340
  });
33864
34341
 
33865
34342
  class PraxisDynamicFormWidgetConfigEditor {
34343
+ crudService = this.optionalInject(GenericCrudService);
34344
+ resourceDiscovery = this.optionalInject(ResourceDiscoveryService);
34345
+ cdr = this.optionalInject(ChangeDetectorRef);
33866
34346
  inputs = null;
33867
34347
  widgetKey;
33868
34348
  appliedValues$;
33869
34349
  appliedSource;
34350
+ appliedResourcePath;
33870
34351
  /** Runtime evidence supplied by the existing component editor context resolver. */
33871
34352
  context = {};
33872
34353
  formEditor;
33873
34354
  isDirty$ = new BehaviorSubject(false);
33874
34355
  isValid$ = new BehaviorSubject(true);
33875
34356
  isBusy$ = new BehaviorSubject(false);
34357
+ schemaWarning = '';
33876
34358
  subscription = new Subscription();
34359
+ schemaSubscription;
34360
+ childBusy = false;
34361
+ schemaLoading = false;
33877
34362
  initialEditorConfig;
33878
34363
  sourceDraft;
34364
+ resourcePathDraft;
34365
+ optionalInject(token) {
34366
+ try {
34367
+ return inject(token, { optional: true });
34368
+ }
34369
+ catch {
34370
+ return null;
34371
+ }
34372
+ }
33879
34373
  get configurationSource() {
33880
34374
  return this.sourceDraft ?? this.inputs?.configPersistenceStrategy ?? 'local-first';
33881
34375
  }
34376
+ get effectiveResourcePath() {
34377
+ if (this.resourcePathDraft !== undefined) {
34378
+ return this.resourcePathDraft;
34379
+ }
34380
+ return String(this.inputs?.resourcePath ?? this.context['resourcePath'] ?? '').trim();
34381
+ }
34382
+ get effectiveEndpointKey() {
34383
+ return this.buildDiscoveryOptions()?.endpointKey;
34384
+ }
34385
+ get effectiveApiUrlEntry() {
34386
+ return this.buildDiscoveryOptions()?.apiUrlEntry;
34387
+ }
34388
+ get effectiveDiscoveryIntent() {
34389
+ return (this.inputs?.schemaSource ?? this.context['schemaSource']) === 'filter'
34390
+ ? 'filter'
34391
+ : 'crud';
34392
+ }
33882
34393
  changeConfigurationSource(value) {
33883
34394
  if (value !== 'local-first' && value !== 'input-first')
33884
34395
  return;
33885
34396
  this.sourceDraft = value;
33886
34397
  this.updateDirtyState(this.formEditor?.isDirty$.value ?? false);
33887
34398
  }
33888
- updateDirtyState(editorDirty) {
33889
- this.isDirty$.next(editorDirty || (this.sourceDraft !== undefined &&
33890
- this.sourceDraft !== (this.appliedSource ?? this.inputs?.configPersistenceStrategy ?? 'local-first')));
33891
- }
33892
- cdr = (() => {
33893
- try {
33894
- return inject(ChangeDetectorRef, { optional: true });
34399
+ onResourceQuickConnect(resourcePath) {
34400
+ const normalized = String(resourcePath ?? '').trim();
34401
+ if (!normalized) {
34402
+ this.onResourceQuickClear();
34403
+ return;
33895
34404
  }
33896
- catch {
33897
- return null;
34405
+ this.resourcePathDraft = normalized;
34406
+ this.updateDirtyState(this.formEditor?.isDirty$.value ?? false);
34407
+ this.loadResourceSchemaFieldsIfNeeded(true);
34408
+ }
34409
+ onResourceQuickClear() {
34410
+ this.schemaSubscription?.unsubscribe();
34411
+ this.schemaLoading = false;
34412
+ this.publishBusy();
34413
+ this.resourcePathDraft = '';
34414
+ this.schemaWarning = '';
34415
+ this.updateDirtyState(this.formEditor?.isDirty$.value ?? false);
34416
+ }
34417
+ baselineResourcePath() {
34418
+ if (this.appliedResourcePath !== undefined) {
34419
+ return this.appliedResourcePath;
33898
34420
  }
33899
- })();
34421
+ return String(this.inputs?.resourcePath ?? this.context['resourcePath'] ?? '').trim();
34422
+ }
34423
+ updateDirtyState(editorDirty) {
34424
+ const sourceChanged = this.sourceDraft !== undefined &&
34425
+ this.sourceDraft !==
34426
+ (this.appliedSource ?? this.inputs?.configPersistenceStrategy ?? 'local-first');
34427
+ const resourceChanged = this.resourcePathDraft !== undefined &&
34428
+ this.resourcePathDraft !== this.baselineResourcePath();
34429
+ this.isDirty$.next(editorDirty || sourceChanged || resourceChanged);
34430
+ }
34431
+ publishBusy() {
34432
+ this.isBusy$.next(this.childBusy || this.schemaLoading);
34433
+ }
33900
34434
  ngAfterViewInit() {
33901
34435
  if (!this.formEditor) {
33902
34436
  return;
@@ -33909,9 +34443,14 @@ class PraxisDynamicFormWidgetConfigEditor {
33909
34443
  this.initialEditorConfig = structuredClone(parseLegacyOrDynamicFormDocument(this.formEditor.getSettingsValue?.() ?? this.createDocumentFromInputs()).config);
33910
34444
  this.subscription.add(this.formEditor.isDirty$.subscribe((value) => this.updateDirtyState(value)));
33911
34445
  this.subscription.add(this.formEditor.isValid$.subscribe((value) => this.isValid$.next(value)));
33912
- this.subscription.add(this.formEditor.isBusy$.subscribe((value) => this.isBusy$.next(value)));
34446
+ this.subscription.add(this.formEditor.isBusy$.subscribe((value) => {
34447
+ this.childBusy = value;
34448
+ this.publishBusy();
34449
+ }));
34450
+ this.loadResourceSchemaFieldsIfNeeded();
33913
34451
  }
33914
34452
  ngOnDestroy() {
34453
+ this.schemaSubscription?.unsubscribe();
33915
34454
  this.subscription.unsubscribe();
33916
34455
  }
33917
34456
  getSettingsValue() {
@@ -33925,25 +34464,173 @@ class PraxisDynamicFormWidgetConfigEditor {
33925
34464
  return;
33926
34465
  // The owner acknowledges the current draft only after committing its inputs.
33927
34466
  this.appliedSource = this.configurationSource;
34467
+ if (this.resourcePathDraft !== undefined) {
34468
+ this.appliedResourcePath = this.resourcePathDraft;
34469
+ }
33928
34470
  this.formEditor?.acceptAppliedValue(this.formEditor.getSettingsValue());
33929
34471
  this.updateDirtyState(this.formEditor?.isDirty$.value ?? false);
33930
34472
  }
33931
34473
  reset() {
34474
+ this.schemaSubscription?.unsubscribe();
34475
+ this.schemaLoading = false;
34476
+ this.publishBusy();
33932
34477
  this.sourceDraft = undefined;
34478
+ this.resourcePathDraft = undefined;
33933
34479
  this.formEditor?.reset?.();
33934
34480
  this.updateDirtyState(this.formEditor?.isDirty$.value ?? false);
34481
+ this.loadResourceSchemaFieldsIfNeeded();
33935
34482
  }
33936
34483
  initializeChildEditor() {
33937
34484
  const document = this.createDocumentFromInputs();
34485
+ this.applyBaselineDocumentToChildEditor(document, 'widget-config-init');
34486
+ }
34487
+ applyBaselineDocumentToChildEditor(document, reason) {
33938
34488
  const serialized = serializeDynamicFormAuthoringDocument(document);
33939
34489
  const editor = this.formEditor;
33940
34490
  editor.onJsonConfigChange?.(serialized);
33941
34491
  editor.initialDocument = document;
33942
34492
  editor.initialConfig = document.config;
33943
- editor.updateDirtyState?.('widget-config-init');
34493
+ editor.updateDirtyState?.(reason);
33944
34494
  editor.isDirty$?.next(false);
33945
34495
  editor.isValid$?.next(true);
33946
34496
  }
34497
+ loadResourceSchemaFieldsIfNeeded(forceReload = false) {
34498
+ if (!this.formEditor) {
34499
+ return;
34500
+ }
34501
+ const resourcePath = this.effectiveResourcePath;
34502
+ const currentDoc = parseLegacyOrDynamicFormDocument(this.formEditor.getSettingsValue?.() ?? this.createDocumentFromInputs());
34503
+ if (!resourcePath ||
34504
+ (!forceReload &&
34505
+ Array.isArray(currentDoc.config.fieldMetadata) &&
34506
+ currentDoc.config.fieldMetadata.length > 0)) {
34507
+ return;
34508
+ }
34509
+ const isFilterSource = (this.inputs?.schemaSource ?? this.context['schemaSource']) === 'filter';
34510
+ const discoveryOptions = this.buildDiscoveryOptions();
34511
+ const discoveryMethodName = isFilterSource
34512
+ ? 'getResourceFilterSchemaFields'
34513
+ : 'getResourceSchemaFields';
34514
+ const canLoadViaDiscovery = typeof this.resourceDiscovery?.[discoveryMethodName] === 'function' &&
34515
+ (typeof this.resourceDiscovery?.canResolveSchemas !== 'function' ||
34516
+ this.resourceDiscovery.canResolveSchemas(discoveryOptions));
34517
+ const canLoadViaCrud = isFilterSource
34518
+ ? typeof this.crudService?.getFilterSchema === 'function' ||
34519
+ typeof this.crudService?.getSchema === 'function'
34520
+ : typeof this.crudService?.getSchema === 'function';
34521
+ if (!canLoadViaCrud && !canLoadViaDiscovery) {
34522
+ return;
34523
+ }
34524
+ const loadFromDiscovery = () => {
34525
+ if (isFilterSource) {
34526
+ return discoveryOptions
34527
+ ? this.resourceDiscovery.getResourceFilterSchemaFields(resourcePath, discoveryOptions)
34528
+ : this.resourceDiscovery.getResourceFilterSchemaFields(resourcePath);
34529
+ }
34530
+ return discoveryOptions
34531
+ ? this.resourceDiscovery.getResourceSchemaFields(resourcePath, discoveryOptions)
34532
+ : this.resourceDiscovery.getResourceSchemaFields(resourcePath);
34533
+ };
34534
+ const loadFromCrud = () => {
34535
+ if (!canLoadViaCrud || !this.crudService) {
34536
+ return of([]);
34537
+ }
34538
+ if (discoveryOptions?.endpointKey || discoveryOptions?.apiUrlEntry) {
34539
+ this.crudService.configure?.(resourcePath, {
34540
+ ...(discoveryOptions.endpointKey ? { endpointKey: discoveryOptions.endpointKey } : {}),
34541
+ ...(discoveryOptions.apiUrlEntry ? { apiUrlEntry: discoveryOptions.apiUrlEntry } : {}),
34542
+ });
34543
+ }
34544
+ else {
34545
+ this.crudService.configure?.(resourcePath);
34546
+ }
34547
+ if (isFilterSource && typeof this.crudService.getFilterSchema === 'function') {
34548
+ return this.crudService.getFilterSchema();
34549
+ }
34550
+ return this.crudService.getSchema();
34551
+ };
34552
+ const isCatalogResourceKey = !resourcePath.includes('/') && resourcePath.includes('.');
34553
+ let source$;
34554
+ if (isCatalogResourceKey && canLoadViaDiscovery) {
34555
+ source$ = loadFromDiscovery().pipe(defaultIfEmpty([]), switchMap$1((defs) => Array.isArray(defs) && defs.length === 0 && canLoadViaCrud
34556
+ ? loadFromCrud()
34557
+ : of(defs)), catchError$1(() => canLoadViaCrud ? loadFromCrud() : of([])));
34558
+ }
34559
+ else if (canLoadViaCrud) {
34560
+ source$ = loadFromCrud().pipe(defaultIfEmpty([]), switchMap$1((defs) => Array.isArray(defs) && defs.length === 0 && canLoadViaDiscovery
34561
+ ? loadFromDiscovery()
34562
+ : of(defs)), catchError$1(() => canLoadViaDiscovery ? loadFromDiscovery() : of([])));
34563
+ }
34564
+ else {
34565
+ source$ = loadFromDiscovery();
34566
+ }
34567
+ this.schemaSubscription?.unsubscribe();
34568
+ this.schemaWarning = '';
34569
+ this.schemaLoading = true;
34570
+ this.publishBusy();
34571
+ this.schemaSubscription = source$
34572
+ .pipe(take$1(1), catchError$1(() => of([])), finalize$1(() => {
34573
+ this.schemaLoading = false;
34574
+ this.publishBusy();
34575
+ this.cdr?.markForCheck();
34576
+ }))
34577
+ .subscribe((defs) => {
34578
+ if (!Array.isArray(defs) || defs.length === 0) {
34579
+ this.schemaWarning = `No schema fields were returned for resource "${resourcePath}".`;
34580
+ this.cdr?.markForCheck();
34581
+ return;
34582
+ }
34583
+ if ((!forceReload && this.isDirty$.value) || !this.formEditor) {
34584
+ return;
34585
+ }
34586
+ const fieldMetadata = mapFieldDefinitionsToMetadata(defs);
34587
+ if (!fieldMetadata.length) {
34588
+ this.schemaWarning = `No schema fields were returned for resource "${resourcePath}".`;
34589
+ this.cdr?.markForCheck();
34590
+ return;
34591
+ }
34592
+ const latestDoc = parseLegacyOrDynamicFormDocument(this.formEditor.getSettingsValue?.() ?? currentDoc);
34593
+ if (!forceReload &&
34594
+ Array.isArray(latestDoc.config.fieldMetadata) &&
34595
+ latestDoc.config.fieldMetadata.length > 0) {
34596
+ return;
34597
+ }
34598
+ const newFieldNames = new Set(fieldMetadata.map((f) => f.name));
34599
+ const existingSectionFields = (latestDoc.config.sections ?? []).flatMap((section) => (section.rows ?? []).flatMap((row) => (row.columns ?? []).flatMap((col) => (col.fields ?? []).map((f) => typeof f === 'string' ? f : f?.name ?? '')))).filter(Boolean);
34600
+ const shouldResetSections = forceReload &&
34601
+ existingSectionFields.length > 0 &&
34602
+ !existingSectionFields.some((fieldName) => newFieldNames.has(fieldName));
34603
+ const nextDocument = {
34604
+ ...latestDoc,
34605
+ config: {
34606
+ ...latestDoc.config,
34607
+ ...(shouldResetSections ? { sections: [] } : {}),
34608
+ fieldMetadata,
34609
+ },
34610
+ };
34611
+ this.applyBaselineDocumentToChildEditor(nextDocument, 'widget-config-schema-hydrate');
34612
+ this.cdr?.detectChanges();
34613
+ this.initialEditorConfig = structuredClone(parseLegacyOrDynamicFormDocument(this.formEditor.getSettingsValue?.() ?? nextDocument).config);
34614
+ this.updateDirtyState(this.formEditor.isDirty$.value);
34615
+ this.cdr?.markForCheck();
34616
+ });
34617
+ }
34618
+ buildDiscoveryOptions() {
34619
+ const rawEndpointKey = this.inputs?.apiEndpointKey ??
34620
+ this.inputs?.endpointKey ??
34621
+ this.context['apiEndpointKey'] ??
34622
+ this.context['endpointKey'];
34623
+ const endpointKey = String(rawEndpointKey ?? '').trim();
34624
+ const apiUrlEntry = this.inputs?.apiUrlEntry ??
34625
+ this.context['apiUrlEntry'];
34626
+ if (!endpointKey && !apiUrlEntry) {
34627
+ return undefined;
34628
+ }
34629
+ return {
34630
+ ...(endpointKey ? { endpointKey } : {}),
34631
+ ...(apiUrlEntry ? { apiUrlEntry } : {}),
34632
+ };
34633
+ }
33947
34634
  buildValue(rawDocument) {
33948
34635
  const document = parseLegacyOrDynamicFormDocument(rawDocument ?? this.createDocumentFromInputs());
33949
34636
  const schemaPrefs = document.contextSnapshot?.schemaPrefs;
@@ -33955,6 +34642,9 @@ class PraxisDynamicFormWidgetConfigEditor {
33955
34642
  inputs: {
33956
34643
  ...(this.inputs ?? {}),
33957
34644
  ...(this.sourceDraft !== undefined ? { configPersistenceStrategy: this.sourceDraft } : {}),
34645
+ ...(this.resourcePathDraft !== undefined
34646
+ ? { resourcePath: this.resourcePathDraft || null }
34647
+ : {}),
33958
34648
  // An explicit layout edit authors sections; merely inspecting generated layout does not.
33959
34649
  ...(layoutChanged && layoutPolicy?.source === 'schema'
33960
34650
  ? { layoutPolicy: { ...layoutPolicy, source: 'authored' } } : {}),
@@ -34002,7 +34692,7 @@ class PraxisDynamicFormWidgetConfigEditor {
34002
34692
  });
34003
34693
  }
34004
34694
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisDynamicFormWidgetConfigEditor, deps: [], target: i0.ɵɵFactoryTarget.Component });
34005
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.14", type: PraxisDynamicFormWidgetConfigEditor, isStandalone: true, selector: "praxis-dynamic-form-widget-config-editor", inputs: { inputs: "inputs", widgetKey: "widgetKey", appliedValues$: "appliedValues$", context: "context" }, providers: [{ provide: SETTINGS_PANEL_DATA, useValue: null }], viewQueries: [{ propertyName: "formEditor", first: true, predicate: ["formEditor"], descendants: true }], ngImport: i0, template: `
34695
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: PraxisDynamicFormWidgetConfigEditor, isStandalone: true, selector: "praxis-dynamic-form-widget-config-editor", inputs: { inputs: "inputs", widgetKey: "widgetKey", appliedValues$: "appliedValues$", context: "context" }, providers: [{ provide: SETTINGS_PANEL_DATA, useValue: null }], viewQueries: [{ propertyName: "formEditor", first: true, predicate: ["formEditor"], descendants: true }], ngImport: i0, template: `
34006
34696
  <section data-testid="dynamic-form-widget-config-editor">
34007
34697
  <div class="configuration-source">
34008
34698
  <mat-form-field appearance="outline" subscriptSizing="dynamic">
@@ -34014,14 +34704,33 @@ class PraxisDynamicFormWidgetConfigEditor {
34014
34704
  <mat-hint>{{ formEditor.tx(configurationSource === 'input-first' ? 'config.general.configurationSource.pageHelp' : 'config.general.configurationSource.preferencesHelp') }}</mat-hint>
34015
34705
  </mat-form-field>
34016
34706
  </div>
34707
+ <div class="resource-quick-connect-host" data-testid="form-widget-resource-quick-connect">
34708
+ <praxis-resource-quick-connect
34709
+ [resourcePath]="effectiveResourcePath"
34710
+ [endpointKey]="effectiveEndpointKey"
34711
+ [apiUrlEntry]="effectiveApiUrlEntry"
34712
+ [intent]="effectiveDiscoveryIntent"
34713
+ (resourcePathChange)="onResourceQuickConnect($event)"
34714
+ ></praxis-resource-quick-connect>
34715
+ </div>
34716
+ @if (schemaWarning) {
34717
+ <p class="form-widget-schema-warning" data-testid="form-widget-schema-warning" role="status">
34718
+ {{ schemaWarning }}
34719
+ </p>
34720
+ }
34017
34721
  <praxis-dynamic-form-config-editor #formEditor />
34018
34722
  <p class="widget-save-hint">{{ formEditor.tx('config.general.pageSaveHint') }}</p>
34019
34723
  </section>
34020
- `, isInline: true, styles: [".configuration-source{padding:var(--p-space-4, 16px)}.configuration-source mat-form-field{width:100%}.widget-save-hint{margin:12px 20px;color:var(--md-sys-color-on-surface-variant);font:var(--md-sys-typescale-body-small)}\n"], dependencies: [{ kind: "component", type: PraxisDynamicFormConfigEditor, selector: "praxis-dynamic-form-config-editor" }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i4$1.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i4$1.MatLabel, selector: "mat-label" }, { kind: "directive", type: i4$1.MatHint, selector: "mat-hint", inputs: ["align", "id"] }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i5$1.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i5$1.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
34724
+ `, isInline: true, styles: [".configuration-source{padding:var(--p-space-4, 16px)}.configuration-source mat-form-field{width:100%}.resource-quick-connect-host{padding:0 var(--p-space-4, 16px) var(--p-space-3, 12px)}.form-widget-schema-warning{margin:0 16px 8px;color:var(--md-sys-color-on-surface-variant);font:var(--md-sys-typescale-body-small)}.widget-save-hint{margin:12px 20px;color:var(--md-sys-color-on-surface-variant);font:var(--md-sys-typescale-body-small)}\n"], dependencies: [{ kind: "component", type: PraxisDynamicFormConfigEditor, selector: "praxis-dynamic-form-config-editor" }, { kind: "component", type: ResourceQuickConnectComponent, selector: "praxis-resource-quick-connect", inputs: ["resourcePath", "endpointKey", "apiUrlEntry", "intent", "resources", "label", "placeholder", "hint", "disabled"], outputs: ["resourcePathChange"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i4$1.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i4$1.MatLabel, selector: "mat-label" }, { kind: "directive", type: i4$1.MatHint, selector: "mat-hint", inputs: ["align", "id"] }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i5$1.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i5$1.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
34021
34725
  }
34022
34726
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisDynamicFormWidgetConfigEditor, decorators: [{
34023
34727
  type: Component,
34024
- args: [{ providers: [{ provide: SETTINGS_PANEL_DATA, useValue: null }], selector: 'praxis-dynamic-form-widget-config-editor', standalone: true, imports: [PraxisDynamicFormConfigEditor, MatFormFieldModule, MatSelectModule], template: `
34728
+ args: [{ providers: [{ provide: SETTINGS_PANEL_DATA, useValue: null }], selector: 'praxis-dynamic-form-widget-config-editor', standalone: true, imports: [
34729
+ PraxisDynamicFormConfigEditor,
34730
+ ResourceQuickConnectComponent,
34731
+ MatFormFieldModule,
34732
+ MatSelectModule,
34733
+ ], template: `
34025
34734
  <section data-testid="dynamic-form-widget-config-editor">
34026
34735
  <div class="configuration-source">
34027
34736
  <mat-form-field appearance="outline" subscriptSizing="dynamic">
@@ -34033,10 +34742,24 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
34033
34742
  <mat-hint>{{ formEditor.tx(configurationSource === 'input-first' ? 'config.general.configurationSource.pageHelp' : 'config.general.configurationSource.preferencesHelp') }}</mat-hint>
34034
34743
  </mat-form-field>
34035
34744
  </div>
34745
+ <div class="resource-quick-connect-host" data-testid="form-widget-resource-quick-connect">
34746
+ <praxis-resource-quick-connect
34747
+ [resourcePath]="effectiveResourcePath"
34748
+ [endpointKey]="effectiveEndpointKey"
34749
+ [apiUrlEntry]="effectiveApiUrlEntry"
34750
+ [intent]="effectiveDiscoveryIntent"
34751
+ (resourcePathChange)="onResourceQuickConnect($event)"
34752
+ ></praxis-resource-quick-connect>
34753
+ </div>
34754
+ @if (schemaWarning) {
34755
+ <p class="form-widget-schema-warning" data-testid="form-widget-schema-warning" role="status">
34756
+ {{ schemaWarning }}
34757
+ </p>
34758
+ }
34036
34759
  <praxis-dynamic-form-config-editor #formEditor />
34037
34760
  <p class="widget-save-hint">{{ formEditor.tx('config.general.pageSaveHint') }}</p>
34038
34761
  </section>
34039
- `, changeDetection: ChangeDetectionStrategy.OnPush, styles: [".configuration-source{padding:var(--p-space-4, 16px)}.configuration-source mat-form-field{width:100%}.widget-save-hint{margin:12px 20px;color:var(--md-sys-color-on-surface-variant);font:var(--md-sys-typescale-body-small)}\n"] }]
34762
+ `, changeDetection: ChangeDetectionStrategy.OnPush, styles: [".configuration-source{padding:var(--p-space-4, 16px)}.configuration-source mat-form-field{width:100%}.resource-quick-connect-host{padding:0 var(--p-space-4, 16px) var(--p-space-3, 12px)}.form-widget-schema-warning{margin:0 16px 8px;color:var(--md-sys-color-on-surface-variant);font:var(--md-sys-typescale-body-small)}.widget-save-hint{margin:12px 20px;color:var(--md-sys-color-on-surface-variant);font:var(--md-sys-typescale-body-small)}\n"] }]
34040
34763
  }], propDecorators: { inputs: [{
34041
34764
  type: Input
34042
34765
  }], widgetKey: [{
@@ -34371,6 +35094,21 @@ const PRAXIS_DYNAMIC_FORM_COMPONENT_METADATA = {
34371
35094
  context: {
34372
35095
  effectiveConfig: materializedInputs?.['config'],
34373
35096
  layoutPolicy: materializedInputs?.['layoutPolicy'],
35097
+ ...(materializedInputs?.['resourcePath'] !== undefined
35098
+ ? { resourcePath: materializedInputs['resourcePath'] }
35099
+ : {}),
35100
+ ...(materializedInputs?.['apiEndpointKey'] !== undefined
35101
+ ? { apiEndpointKey: materializedInputs['apiEndpointKey'] }
35102
+ : {}),
35103
+ ...(materializedInputs?.['endpointKey'] !== undefined
35104
+ ? { endpointKey: materializedInputs['endpointKey'] }
35105
+ : {}),
35106
+ ...(materializedInputs?.['apiUrlEntry'] !== undefined
35107
+ ? { apiUrlEntry: materializedInputs['apiUrlEntry'] }
35108
+ : {}),
35109
+ ...(materializedInputs?.['schemaSource'] !== undefined
35110
+ ? { schemaSource: materializedInputs['schemaSource'] }
35111
+ : {}),
34374
35112
  },
34375
35113
  }),
34376
35114
  title: 'Configurar formulário',