@praxisui/dynamic-form 10.0.0-rc.32 → 10.0.0-rc.34

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,90 @@ 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
+ committedBaselineDocument;
34042
+ crudService = (() => {
34043
+ try {
34044
+ return inject(GenericCrudService, { optional: true });
34045
+ }
34046
+ catch {
34047
+ return null;
34048
+ }
34049
+ })();
34050
+ resourceDiscovery = (() => {
34051
+ try {
34052
+ return inject(ResourceDiscoveryService, { optional: true });
34053
+ }
34054
+ catch {
34055
+ return null;
34056
+ }
34057
+ })();
34058
+ cdr = (() => {
34059
+ try {
34060
+ return inject(ChangeDetectorRef, { optional: true });
34061
+ }
34062
+ catch {
34063
+ return null;
34064
+ }
34065
+ })();
34066
+ get effectiveResourcePath() {
34067
+ if (this.resourcePathDraft !== undefined) {
34068
+ return this.resourcePathDraft;
34069
+ }
34070
+ return String(this.inputs?.resourcePath || '').trim();
34071
+ }
34072
+ get effectiveEndpointKey() {
34073
+ return this.buildDiscoveryOptions()?.endpointKey;
34074
+ }
34075
+ get effectiveApiUrlEntry() {
34076
+ return this.buildDiscoveryOptions()?.apiUrlEntry;
34077
+ }
34078
+ onResourceQuickConnect(resourcePath) {
34079
+ const normalized = String(resourcePath ?? '').trim();
34080
+ if (!normalized) {
34081
+ this.onResourceQuickClear();
34082
+ return;
34083
+ }
34084
+ this.resourcePathDraft = normalized;
34085
+ this.updateDirtyState(this.formEditor?.isDirty$?.value ?? false);
34086
+ if (this.formEditor) {
34087
+ const currentDoc = parseLegacyOrDynamicFormDocument(this.formEditor.getSettingsValue?.() ?? this.createDocumentFromInputs());
34088
+ this.loadFilterSchemaFieldsIfNeeded(normalized, currentDoc, true);
34089
+ }
34090
+ }
34091
+ onResourceQuickClear() {
34092
+ this.schemaSubscription?.unsubscribe();
34093
+ this.schemaLoading = false;
34094
+ this.publishBusy();
34095
+ this.resourcePathDraft = '';
34096
+ this.schemaWarning = '';
34097
+ this.updateDirtyState(this.formEditor?.isDirty$?.value ?? false);
34098
+ }
34099
+ updateDirtyState(editorDirty) {
34100
+ const baseline = String(this.inputs?.resourcePath || '').trim();
34101
+ const resourceChanged = this.resourcePathDraft !== undefined && this.resourcePathDraft !== baseline;
34102
+ this.isDirty$.next(editorDirty || resourceChanged);
34103
+ }
33771
34104
  ngAfterViewInit() {
33772
34105
  if (!this.formEditor) {
33773
34106
  return;
33774
34107
  }
33775
34108
  this.formEditor.supportsPresentationSettings = false;
33776
- this.initializeChildEditor();
33777
- this.subscription.add(this.formEditor.isDirty$.subscribe((value) => this.isDirty$.next(value)));
34109
+ this.subscription.add(this.formEditor.isDirty$.subscribe((value) => this.updateDirtyState(value)));
33778
34110
  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)));
34111
+ this.subscription.add(this.formEditor.isBusy$.subscribe((value) => {
34112
+ this.childBusy = value;
34113
+ this.publishBusy();
34114
+ }));
34115
+ this.initializeChildEditor();
33780
34116
  }
33781
34117
  ngOnDestroy() {
34118
+ this.schemaSubscription?.unsubscribe();
33782
34119
  this.subscription.unsubscribe();
33783
34120
  }
33784
34121
  getSettingsValue() {
@@ -33788,27 +34125,165 @@ class PraxisFilterFormWidgetConfigEditor {
33788
34125
  return this.buildValue(this.formEditor?.onSave?.() ?? this.formEditor?.getSettingsValue());
33789
34126
  }
33790
34127
  reset() {
33791
- this.formEditor?.reset?.();
34128
+ this.schemaSubscription?.unsubscribe();
34129
+ this.schemaLoading = false;
34130
+ this.schemaWarning = '';
34131
+ this.resourcePathDraft = undefined;
34132
+ this.publishBusy();
34133
+ if (typeof this.formEditor?.reset === 'function') {
34134
+ this.formEditor.reset();
34135
+ }
34136
+ else if (this.formEditor && this.committedBaselineDocument) {
34137
+ this.applyBaselineDocumentToChildEditor(structuredClone(this.committedBaselineDocument), 'filter-form-widget-config-reset');
34138
+ }
34139
+ this.updateDirtyState(this.formEditor?.isDirty$?.value ?? false);
34140
+ }
34141
+ publishBusy() {
34142
+ this.isBusy$.next(this.childBusy || this.schemaLoading);
33792
34143
  }
33793
34144
  initializeChildEditor() {
33794
34145
  const document = this.createDocumentFromInputs();
34146
+ this.committedBaselineDocument = structuredClone(document);
34147
+ this.applyBaselineDocumentToChildEditor(document, 'filter-form-widget-config-init');
34148
+ const resourcePath = this.effectiveResourcePath;
34149
+ if (resourcePath &&
34150
+ (!Array.isArray(document.config.fieldMetadata) || document.config.fieldMetadata.length === 0)) {
34151
+ this.loadFilterSchemaFieldsIfNeeded(resourcePath, document);
34152
+ }
34153
+ }
34154
+ applyBaselineDocumentToChildEditor(document, reason) {
33795
34155
  const serialized = serializeDynamicFormAuthoringDocument(document);
33796
34156
  const editor = this.formEditor;
33797
34157
  editor.onJsonConfigChange?.(serialized);
33798
34158
  editor.initialDocument = document;
33799
34159
  editor.initialConfig = document.config;
33800
- editor.updateDirtyState?.('filter-form-widget-config-init');
34160
+ editor.updateDirtyState?.(reason);
33801
34161
  editor.isDirty$?.next(false);
33802
34162
  editor.isValid$?.next(true);
33803
34163
  }
34164
+ applyDraftDocumentToChildEditor(document, reason) {
34165
+ const serialized = serializeDynamicFormAuthoringDocument(document);
34166
+ const editor = this.formEditor;
34167
+ editor.onJsonConfigChange?.(serialized);
34168
+ editor.updateDirtyState?.(reason);
34169
+ }
34170
+ loadFilterSchemaFieldsIfNeeded(resourcePath, baselineDocument, forceReload = false) {
34171
+ const discoveryOptions = this.buildDiscoveryOptions();
34172
+ const canLoadViaDiscovery = typeof this.resourceDiscovery?.getResourceFilterSchemaFields === 'function' &&
34173
+ (typeof this.resourceDiscovery.canResolveSchemas !== 'function' ||
34174
+ this.resourceDiscovery.canResolveSchemas(discoveryOptions));
34175
+ const canLoadViaCrud = typeof this.crudService?.getFilterSchema === 'function' ||
34176
+ typeof this.crudService?.getSchema === 'function';
34177
+ if (!canLoadViaCrud && !canLoadViaDiscovery) {
34178
+ return;
34179
+ }
34180
+ const loadFromDiscovery = () => discoveryOptions
34181
+ ? this.resourceDiscovery.getResourceFilterSchemaFields(resourcePath, discoveryOptions)
34182
+ : this.resourceDiscovery.getResourceFilterSchemaFields(resourcePath);
34183
+ const loadFromCrud = () => {
34184
+ if (!canLoadViaCrud || !this.crudService) {
34185
+ return of([]);
34186
+ }
34187
+ if (discoveryOptions?.endpointKey || discoveryOptions?.apiUrlEntry) {
34188
+ this.crudService.configure?.(resourcePath, {
34189
+ ...(discoveryOptions.endpointKey ? { endpointKey: discoveryOptions.endpointKey } : {}),
34190
+ ...(discoveryOptions.apiUrlEntry ? { apiUrlEntry: discoveryOptions.apiUrlEntry } : {}),
34191
+ });
34192
+ }
34193
+ else {
34194
+ this.crudService.configure?.(resourcePath);
34195
+ }
34196
+ return typeof this.crudService.getFilterSchema === 'function'
34197
+ ? this.crudService.getFilterSchema()
34198
+ : this.crudService.getSchema();
34199
+ };
34200
+ const isCatalogResourceKey = !resourcePath.includes('/') && resourcePath.includes('.');
34201
+ let source$;
34202
+ if (isCatalogResourceKey && canLoadViaDiscovery) {
34203
+ source$ = loadFromDiscovery().pipe(defaultIfEmpty([]), switchMap$1((defs) => Array.isArray(defs) && defs.length === 0 && canLoadViaCrud
34204
+ ? loadFromCrud()
34205
+ : of(defs)), catchError$1(() => (canLoadViaCrud ? loadFromCrud() : of([]))));
34206
+ }
34207
+ else if (canLoadViaCrud) {
34208
+ source$ = loadFromCrud().pipe(defaultIfEmpty([]), switchMap$1((defs) => Array.isArray(defs) && defs.length === 0 && canLoadViaDiscovery
34209
+ ? loadFromDiscovery()
34210
+ : of(defs)), catchError$1(() => (canLoadViaDiscovery ? loadFromDiscovery() : of([]))));
34211
+ }
34212
+ else {
34213
+ source$ = loadFromDiscovery();
34214
+ }
34215
+ this.schemaSubscription?.unsubscribe();
34216
+ this.schemaWarning = '';
34217
+ this.schemaLoading = true;
34218
+ this.publishBusy();
34219
+ this.schemaSubscription = source$
34220
+ .pipe(take$1(1), catchError$1(() => of([])), finalize$1(() => {
34221
+ this.schemaLoading = false;
34222
+ this.publishBusy();
34223
+ this.cdr?.markForCheck();
34224
+ }))
34225
+ .subscribe((defs) => {
34226
+ if (!Array.isArray(defs) || defs.length === 0) {
34227
+ this.schemaWarning = `No filter schema fields were returned for resource "${resourcePath}".`;
34228
+ this.cdr?.markForCheck();
34229
+ return;
34230
+ }
34231
+ if ((!forceReload && this.isDirty$.value) || !this.formEditor) {
34232
+ return;
34233
+ }
34234
+ const fieldMetadata = mapFieldDefinitionsToMetadata(defs);
34235
+ if (!fieldMetadata.length) {
34236
+ this.schemaWarning = `No filter schema fields were returned for resource "${resourcePath}".`;
34237
+ this.cdr?.markForCheck();
34238
+ return;
34239
+ }
34240
+ const newFieldNames = new Set(fieldMetadata.map((f) => f.name));
34241
+ 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);
34242
+ const shouldResetSections = forceReload &&
34243
+ existingSectionFields.length > 0 &&
34244
+ !existingSectionFields.some((fieldName) => newFieldNames.has(fieldName));
34245
+ const nextDocument = {
34246
+ ...baselineDocument,
34247
+ config: {
34248
+ ...baselineDocument.config,
34249
+ ...(shouldResetSections ? { sections: [] } : {}),
34250
+ fieldMetadata,
34251
+ },
34252
+ };
34253
+ if (forceReload) {
34254
+ this.applyDraftDocumentToChildEditor(nextDocument, 'filter-form-widget-config-schema-reload');
34255
+ }
34256
+ else {
34257
+ this.committedBaselineDocument = structuredClone(nextDocument);
34258
+ this.applyBaselineDocumentToChildEditor(nextDocument, 'filter-form-widget-config-schema');
34259
+ }
34260
+ this.updateDirtyState(this.formEditor.isDirty$.value);
34261
+ this.cdr?.markForCheck();
34262
+ });
34263
+ }
34264
+ buildDiscoveryOptions() {
34265
+ const rawEndpointKey = this.inputs?.apiEndpointKey ?? this.inputs?.endpointKey;
34266
+ const endpointKey = String(rawEndpointKey ?? '').trim();
34267
+ const apiUrlEntry = this.inputs?.apiUrlEntry;
34268
+ if (!endpointKey && !apiUrlEntry) {
34269
+ return undefined;
34270
+ }
34271
+ return {
34272
+ ...(endpointKey ? { endpointKey } : {}),
34273
+ ...(apiUrlEntry ? { apiUrlEntry } : {}),
34274
+ };
34275
+ }
33804
34276
  buildValue(rawDocument) {
33805
34277
  const document = parseLegacyOrDynamicFormDocument(rawDocument ?? this.createDocumentFromInputs());
34278
+ const resolvedResourcePath = this.resourcePathDraft !== undefined
34279
+ ? this.resourcePathDraft || undefined
34280
+ : this.inputs?.resourcePath;
33806
34281
  return {
33807
34282
  inputs: {
33808
34283
  ...(this.inputs ?? {}),
33809
34284
  config: document.config,
33810
34285
  formId: this.inputs?.formId ?? this.widgetKey,
33811
- resourcePath: this.inputs?.resourcePath,
34286
+ resourcePath: resolvedResourcePath,
33812
34287
  mode: document.bindings?.mode ?? this.inputs?.mode ?? 'create',
33813
34288
  },
33814
34289
  };
@@ -33829,25 +34304,47 @@ class PraxisFilterFormWidgetConfigEditor {
33829
34304
  : undefined;
33830
34305
  }
33831
34306
  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: `
34307
+ 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
34308
  <section data-testid="filter-form-widget-config-editor">
34309
+ <div class="filter-form-resource-quick-connect" data-testid="filter-form-resource-quick-connect">
34310
+ <praxis-resource-quick-connect
34311
+ [resourcePath]="effectiveResourcePath"
34312
+ [endpointKey]="effectiveEndpointKey"
34313
+ [apiUrlEntry]="effectiveApiUrlEntry"
34314
+ intent="filter"
34315
+ (resourcePathChange)="onResourceQuickConnect($event)"
34316
+ ></praxis-resource-quick-connect>
34317
+ </div>
34318
+ @if (schemaWarning) {
34319
+ <p class="filter-form-editor-schema-warning" data-testid="filter-form-schema-warning" role="status">
34320
+ {{ schemaWarning }}
34321
+ </p>
34322
+ }
33834
34323
  <praxis-dynamic-form-config-editor #formEditor />
33835
34324
  </section>
33836
- `, isInline: true, dependencies: [{ kind: "component", type: PraxisDynamicFormConfigEditor, selector: "praxis-dynamic-form-config-editor" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
34325
+ `, isInline: true, styles: [".filter-form-resource-quick-connect{padding:var(--p-space-4, 16px) var(--p-space-4, 16px) var(--p-space-2, 8px)}.filter-form-editor-schema-warning{margin:0 16px 8px;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", "resourceSelected"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
33837
34326
  }
33838
34327
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisFilterFormWidgetConfigEditor, decorators: [{
33839
34328
  type: Component,
33840
- args: [{
33841
- selector: 'praxis-filter-form-widget-config-editor',
33842
- standalone: true,
33843
- imports: [PraxisDynamicFormConfigEditor],
33844
- template: `
34329
+ args: [{ providers: [{ provide: SETTINGS_PANEL_DATA, useValue: null }], selector: 'praxis-filter-form-widget-config-editor', standalone: true, imports: [PraxisDynamicFormConfigEditor, ResourceQuickConnectComponent], template: `
33845
34330
  <section data-testid="filter-form-widget-config-editor">
34331
+ <div class="filter-form-resource-quick-connect" data-testid="filter-form-resource-quick-connect">
34332
+ <praxis-resource-quick-connect
34333
+ [resourcePath]="effectiveResourcePath"
34334
+ [endpointKey]="effectiveEndpointKey"
34335
+ [apiUrlEntry]="effectiveApiUrlEntry"
34336
+ intent="filter"
34337
+ (resourcePathChange)="onResourceQuickConnect($event)"
34338
+ ></praxis-resource-quick-connect>
34339
+ </div>
34340
+ @if (schemaWarning) {
34341
+ <p class="filter-form-editor-schema-warning" data-testid="filter-form-schema-warning" role="status">
34342
+ {{ schemaWarning }}
34343
+ </p>
34344
+ }
33846
34345
  <praxis-dynamic-form-config-editor #formEditor />
33847
34346
  </section>
33848
- `,
33849
- changeDetection: ChangeDetectionStrategy.OnPush,
33850
- }]
34347
+ `, 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)}.filter-form-editor-schema-warning{margin:0 16px 8px;color:var(--md-sys-color-on-surface-variant);font:var(--md-sys-typescale-body-small)}\n"] }]
33851
34348
  }], propDecorators: { inputs: [{
33852
34349
  type: Input
33853
34350
  }], widgetKey: [{
@@ -33863,40 +34360,99 @@ var praxisFilterFormWidgetConfigEditor = /*#__PURE__*/Object.freeze({
33863
34360
  });
33864
34361
 
33865
34362
  class PraxisDynamicFormWidgetConfigEditor {
34363
+ crudService = this.optionalInject(GenericCrudService);
34364
+ resourceDiscovery = this.optionalInject(ResourceDiscoveryService);
34365
+ cdr = this.optionalInject(ChangeDetectorRef);
33866
34366
  inputs = null;
33867
34367
  widgetKey;
33868
34368
  appliedValues$;
33869
34369
  appliedSource;
34370
+ appliedResourcePath;
33870
34371
  /** Runtime evidence supplied by the existing component editor context resolver. */
33871
34372
  context = {};
33872
34373
  formEditor;
33873
34374
  isDirty$ = new BehaviorSubject(false);
33874
34375
  isValid$ = new BehaviorSubject(true);
33875
34376
  isBusy$ = new BehaviorSubject(false);
34377
+ schemaWarning = '';
33876
34378
  subscription = new Subscription();
34379
+ schemaSubscription;
34380
+ childBusy = false;
34381
+ schemaLoading = false;
33877
34382
  initialEditorConfig;
34383
+ committedBaselineConfig;
34384
+ committedBaselineDocument;
33878
34385
  sourceDraft;
34386
+ resourcePathDraft;
34387
+ optionalInject(token) {
34388
+ try {
34389
+ return inject(token, { optional: true });
34390
+ }
34391
+ catch {
34392
+ return null;
34393
+ }
34394
+ }
33879
34395
  get configurationSource() {
33880
34396
  return this.sourceDraft ?? this.inputs?.configPersistenceStrategy ?? 'local-first';
33881
34397
  }
34398
+ get effectiveResourcePath() {
34399
+ if (this.resourcePathDraft !== undefined) {
34400
+ return this.resourcePathDraft;
34401
+ }
34402
+ return String(this.inputs?.resourcePath ?? this.context['resourcePath'] ?? '').trim();
34403
+ }
34404
+ get effectiveEndpointKey() {
34405
+ return this.buildDiscoveryOptions()?.endpointKey;
34406
+ }
34407
+ get effectiveApiUrlEntry() {
34408
+ return this.buildDiscoveryOptions()?.apiUrlEntry;
34409
+ }
34410
+ get effectiveDiscoveryIntent() {
34411
+ return (this.inputs?.schemaSource ?? this.context['schemaSource']) === 'filter'
34412
+ ? 'filter'
34413
+ : 'crud';
34414
+ }
33882
34415
  changeConfigurationSource(value) {
33883
34416
  if (value !== 'local-first' && value !== 'input-first')
33884
34417
  return;
33885
34418
  this.sourceDraft = value;
33886
- this.updateDirtyState(this.formEditor?.isDirty$.value ?? false);
33887
- }
33888
- updateDirtyState(editorDirty) {
33889
- this.isDirty$.next(editorDirty || (this.sourceDraft !== undefined &&
33890
- this.sourceDraft !== (this.appliedSource ?? this.inputs?.configPersistenceStrategy ?? 'local-first')));
34419
+ this.updateDirtyState(this.formEditor?.isDirty$?.value ?? false);
33891
34420
  }
33892
- cdr = (() => {
33893
- try {
33894
- return inject(ChangeDetectorRef, { optional: true });
34421
+ onResourceQuickConnect(resourcePath) {
34422
+ const normalized = String(resourcePath ?? '').trim();
34423
+ if (!normalized) {
34424
+ this.onResourceQuickClear();
34425
+ return;
33895
34426
  }
33896
- catch {
33897
- return null;
34427
+ this.resourcePathDraft = normalized;
34428
+ this.updateDirtyState(this.formEditor?.isDirty$?.value ?? false);
34429
+ this.loadResourceSchemaFieldsIfNeeded(true);
34430
+ }
34431
+ onResourceQuickClear() {
34432
+ this.schemaSubscription?.unsubscribe();
34433
+ this.schemaLoading = false;
34434
+ this.publishBusy();
34435
+ this.resourcePathDraft = '';
34436
+ this.schemaWarning = '';
34437
+ this.updateDirtyState(this.formEditor?.isDirty$?.value ?? false);
34438
+ }
34439
+ baselineResourcePath() {
34440
+ if (this.appliedResourcePath !== undefined) {
34441
+ return this.appliedResourcePath;
33898
34442
  }
33899
- })();
34443
+ return String(this.inputs?.resourcePath ?? this.context['resourcePath'] ?? '').trim();
34444
+ }
34445
+ updateDirtyState(editorDirty) {
34446
+ const sourceChanged = this.sourceDraft !== undefined &&
34447
+ this.sourceDraft !==
34448
+ (this.appliedSource ?? this.inputs?.configPersistenceStrategy ?? 'local-first');
34449
+ const resourceChanged = this.resourcePathDraft !== undefined &&
34450
+ this.resourcePathDraft !== this.baselineResourcePath();
34451
+ this.isDirty$.next(editorDirty || sourceChanged || resourceChanged);
34452
+ }
34453
+ publishBusy() {
34454
+ this.isBusy$.next(this.childBusy || this.schemaLoading);
34455
+ }
33900
34456
  ngAfterViewInit() {
33901
34457
  if (!this.formEditor) {
33902
34458
  return;
@@ -33906,12 +34462,20 @@ class PraxisDynamicFormWidgetConfigEditor {
33906
34462
  this.subscription.add(this.appliedValues$.subscribe(value => this.acceptAppliedValue(value)));
33907
34463
  // Finish child template normalization before capturing the authoring baseline.
33908
34464
  this.cdr?.detectChanges();
33909
- this.initialEditorConfig = structuredClone(parseLegacyOrDynamicFormDocument(this.formEditor.getSettingsValue?.() ?? this.createDocumentFromInputs()).config);
34465
+ const initialDoc = parseLegacyOrDynamicFormDocument(this.formEditor.getSettingsValue?.() ?? this.createDocumentFromInputs());
34466
+ this.initialEditorConfig = structuredClone(initialDoc.config);
34467
+ this.committedBaselineConfig = structuredClone(initialDoc.config);
34468
+ this.committedBaselineDocument = structuredClone(initialDoc);
33910
34469
  this.subscription.add(this.formEditor.isDirty$.subscribe((value) => this.updateDirtyState(value)));
33911
34470
  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)));
34471
+ this.subscription.add(this.formEditor.isBusy$.subscribe((value) => {
34472
+ this.childBusy = value;
34473
+ this.publishBusy();
34474
+ }));
34475
+ this.loadResourceSchemaFieldsIfNeeded();
33913
34476
  }
33914
34477
  ngOnDestroy() {
34478
+ this.schemaSubscription?.unsubscribe();
33915
34479
  this.subscription.unsubscribe();
33916
34480
  }
33917
34481
  getSettingsValue() {
@@ -33925,25 +34489,209 @@ class PraxisDynamicFormWidgetConfigEditor {
33925
34489
  return;
33926
34490
  // The owner acknowledges the current draft only after committing its inputs.
33927
34491
  this.appliedSource = this.configurationSource;
33928
- this.formEditor?.acceptAppliedValue(this.formEditor.getSettingsValue());
33929
- this.updateDirtyState(this.formEditor?.isDirty$.value ?? false);
34492
+ if (this.resourcePathDraft !== undefined) {
34493
+ this.appliedResourcePath = this.resourcePathDraft;
34494
+ }
34495
+ const currentSettings = this.formEditor?.getSettingsValue();
34496
+ this.formEditor?.acceptAppliedValue(currentSettings);
34497
+ const committedDoc = parseLegacyOrDynamicFormDocument(currentSettings ?? this.createDocumentFromInputs());
34498
+ this.initialEditorConfig = structuredClone(committedDoc.config);
34499
+ this.committedBaselineConfig = structuredClone(committedDoc.config);
34500
+ this.committedBaselineDocument = structuredClone(committedDoc);
34501
+ this.updateDirtyState(this.formEditor?.isDirty$?.value ?? false);
33930
34502
  }
33931
34503
  reset() {
34504
+ this.schemaSubscription?.unsubscribe();
34505
+ this.schemaLoading = false;
34506
+ this.schemaWarning = '';
34507
+ this.publishBusy();
33932
34508
  this.sourceDraft = undefined;
33933
- this.formEditor?.reset?.();
33934
- this.updateDirtyState(this.formEditor?.isDirty$.value ?? false);
34509
+ this.resourcePathDraft = undefined;
34510
+ if (typeof this.formEditor?.reset === 'function') {
34511
+ this.formEditor.reset();
34512
+ }
34513
+ else if (this.formEditor && this.committedBaselineDocument) {
34514
+ this.applyBaselineDocumentToChildEditor(structuredClone(this.committedBaselineDocument), 'widget-config-reset');
34515
+ }
34516
+ if (this.committedBaselineConfig) {
34517
+ this.initialEditorConfig = structuredClone(this.committedBaselineConfig);
34518
+ }
34519
+ this.updateDirtyState(this.formEditor?.isDirty$?.value ?? false);
34520
+ this.loadResourceSchemaFieldsIfNeeded();
33935
34521
  }
33936
34522
  initializeChildEditor() {
33937
34523
  const document = this.createDocumentFromInputs();
34524
+ this.applyBaselineDocumentToChildEditor(document, 'widget-config-init');
34525
+ }
34526
+ applyBaselineDocumentToChildEditor(document, reason) {
33938
34527
  const serialized = serializeDynamicFormAuthoringDocument(document);
33939
34528
  const editor = this.formEditor;
33940
34529
  editor.onJsonConfigChange?.(serialized);
33941
34530
  editor.initialDocument = document;
33942
34531
  editor.initialConfig = document.config;
33943
- editor.updateDirtyState?.('widget-config-init');
34532
+ editor.updateDirtyState?.(reason);
33944
34533
  editor.isDirty$?.next(false);
33945
34534
  editor.isValid$?.next(true);
33946
34535
  }
34536
+ applyDraftDocumentToChildEditor(document, reason) {
34537
+ const serialized = serializeDynamicFormAuthoringDocument(document);
34538
+ const editor = this.formEditor;
34539
+ editor.onJsonConfigChange?.(serialized);
34540
+ editor.updateDirtyState?.(reason);
34541
+ }
34542
+ loadResourceSchemaFieldsIfNeeded(forceReload = false) {
34543
+ if (!this.formEditor) {
34544
+ return;
34545
+ }
34546
+ const resourcePath = this.effectiveResourcePath;
34547
+ const currentDoc = parseLegacyOrDynamicFormDocument(this.formEditor.getSettingsValue?.() ?? this.createDocumentFromInputs());
34548
+ if (!resourcePath ||
34549
+ (!forceReload &&
34550
+ Array.isArray(currentDoc.config.fieldMetadata) &&
34551
+ currentDoc.config.fieldMetadata.length > 0)) {
34552
+ return;
34553
+ }
34554
+ const isFilterSource = (this.inputs?.schemaSource ?? this.context['schemaSource']) === 'filter';
34555
+ const discoveryOptions = this.buildDiscoveryOptions();
34556
+ const discoveryMethodName = isFilterSource
34557
+ ? 'getResourceFilterSchemaFields'
34558
+ : 'getResourceSchemaFields';
34559
+ const canLoadViaDiscovery = typeof this.resourceDiscovery?.[discoveryMethodName] === 'function' &&
34560
+ (typeof this.resourceDiscovery?.canResolveSchemas !== 'function' ||
34561
+ this.resourceDiscovery.canResolveSchemas(discoveryOptions));
34562
+ const canLoadViaCrud = isFilterSource
34563
+ ? typeof this.crudService?.getFilterSchema === 'function' ||
34564
+ typeof this.crudService?.getSchema === 'function'
34565
+ : typeof this.crudService?.getSchema === 'function';
34566
+ if (!canLoadViaCrud && !canLoadViaDiscovery) {
34567
+ return;
34568
+ }
34569
+ const loadFromDiscovery = () => {
34570
+ if (isFilterSource) {
34571
+ return discoveryOptions
34572
+ ? this.resourceDiscovery.getResourceFilterSchemaFields(resourcePath, discoveryOptions)
34573
+ : this.resourceDiscovery.getResourceFilterSchemaFields(resourcePath);
34574
+ }
34575
+ return discoveryOptions
34576
+ ? this.resourceDiscovery.getResourceSchemaFields(resourcePath, discoveryOptions)
34577
+ : this.resourceDiscovery.getResourceSchemaFields(resourcePath);
34578
+ };
34579
+ const loadFromCrud = () => {
34580
+ if (!canLoadViaCrud || !this.crudService) {
34581
+ return of([]);
34582
+ }
34583
+ if (discoveryOptions?.endpointKey || discoveryOptions?.apiUrlEntry) {
34584
+ this.crudService.configure?.(resourcePath, {
34585
+ ...(discoveryOptions.endpointKey ? { endpointKey: discoveryOptions.endpointKey } : {}),
34586
+ ...(discoveryOptions.apiUrlEntry ? { apiUrlEntry: discoveryOptions.apiUrlEntry } : {}),
34587
+ });
34588
+ }
34589
+ else {
34590
+ this.crudService.configure?.(resourcePath);
34591
+ }
34592
+ if (isFilterSource && typeof this.crudService.getFilterSchema === 'function') {
34593
+ return this.crudService.getFilterSchema();
34594
+ }
34595
+ return this.crudService.getSchema();
34596
+ };
34597
+ const isCatalogResourceKey = !resourcePath.includes('/') && resourcePath.includes('.');
34598
+ let source$;
34599
+ if (isCatalogResourceKey && canLoadViaDiscovery) {
34600
+ source$ = loadFromDiscovery().pipe(defaultIfEmpty([]), switchMap$1((defs) => Array.isArray(defs) && defs.length === 0 && canLoadViaCrud
34601
+ ? loadFromCrud()
34602
+ : of(defs)), catchError$1(() => canLoadViaCrud ? loadFromCrud() : of([])));
34603
+ }
34604
+ else if (canLoadViaCrud) {
34605
+ source$ = loadFromCrud().pipe(defaultIfEmpty([]), switchMap$1((defs) => Array.isArray(defs) && defs.length === 0 && canLoadViaDiscovery
34606
+ ? loadFromDiscovery()
34607
+ : of(defs)), catchError$1(() => canLoadViaDiscovery ? loadFromDiscovery() : of([])));
34608
+ }
34609
+ else {
34610
+ source$ = loadFromDiscovery();
34611
+ }
34612
+ this.schemaSubscription?.unsubscribe();
34613
+ this.schemaWarning = '';
34614
+ this.schemaLoading = true;
34615
+ this.publishBusy();
34616
+ this.schemaSubscription = source$
34617
+ .pipe(take$1(1), catchError$1(() => of([])), finalize$1(() => {
34618
+ this.schemaLoading = false;
34619
+ this.publishBusy();
34620
+ this.cdr?.markForCheck();
34621
+ }))
34622
+ .subscribe((defs) => {
34623
+ if (!Array.isArray(defs) || defs.length === 0) {
34624
+ this.schemaWarning = `No schema fields were returned for resource "${resourcePath}".`;
34625
+ this.cdr?.markForCheck();
34626
+ return;
34627
+ }
34628
+ if ((!forceReload && this.isDirty$.value) || !this.formEditor) {
34629
+ return;
34630
+ }
34631
+ const fieldMetadata = mapFieldDefinitionsToMetadata(defs);
34632
+ if (!fieldMetadata.length) {
34633
+ this.schemaWarning = `No schema fields were returned for resource "${resourcePath}".`;
34634
+ this.cdr?.markForCheck();
34635
+ return;
34636
+ }
34637
+ const latestDoc = parseLegacyOrDynamicFormDocument(this.formEditor.getSettingsValue?.() ?? currentDoc);
34638
+ if (!forceReload &&
34639
+ Array.isArray(latestDoc.config.fieldMetadata) &&
34640
+ latestDoc.config.fieldMetadata.length > 0) {
34641
+ return;
34642
+ }
34643
+ const newFieldNames = new Set(fieldMetadata.map((f) => f.name));
34644
+ 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);
34645
+ const shouldResetSections = forceReload &&
34646
+ existingSectionFields.length > 0 &&
34647
+ !existingSectionFields.some((fieldName) => newFieldNames.has(fieldName));
34648
+ const nextDocument = {
34649
+ ...latestDoc,
34650
+ config: {
34651
+ ...latestDoc.config,
34652
+ ...(shouldResetSections ? { sections: [] } : {}),
34653
+ fieldMetadata,
34654
+ },
34655
+ };
34656
+ if (forceReload) {
34657
+ this.applyDraftDocumentToChildEditor(nextDocument, 'widget-config-schema-reload');
34658
+ this.cdr?.detectChanges();
34659
+ const hydratedConfig = structuredClone(parseLegacyOrDynamicFormDocument(this.formEditor.getSettingsValue?.() ?? nextDocument).config);
34660
+ this.initialEditorConfig = {
34661
+ ...hydratedConfig,
34662
+ ...(this.committedBaselineConfig?.sections
34663
+ ? { sections: structuredClone(this.committedBaselineConfig.sections) }
34664
+ : {}),
34665
+ };
34666
+ }
34667
+ else {
34668
+ this.applyBaselineDocumentToChildEditor(nextDocument, 'widget-config-schema-hydrate');
34669
+ this.cdr?.detectChanges();
34670
+ const hydratedDoc = parseLegacyOrDynamicFormDocument(this.formEditor.getSettingsValue?.() ?? nextDocument);
34671
+ this.initialEditorConfig = structuredClone(hydratedDoc.config);
34672
+ this.committedBaselineConfig = structuredClone(hydratedDoc.config);
34673
+ this.committedBaselineDocument = structuredClone(hydratedDoc);
34674
+ }
34675
+ this.updateDirtyState(this.formEditor.isDirty$.value);
34676
+ this.cdr?.markForCheck();
34677
+ });
34678
+ }
34679
+ buildDiscoveryOptions() {
34680
+ const rawEndpointKey = this.inputs?.apiEndpointKey ??
34681
+ this.inputs?.endpointKey ??
34682
+ this.context['apiEndpointKey'] ??
34683
+ this.context['endpointKey'];
34684
+ const endpointKey = String(rawEndpointKey ?? '').trim();
34685
+ const apiUrlEntry = this.inputs?.apiUrlEntry ??
34686
+ this.context['apiUrlEntry'];
34687
+ if (!endpointKey && !apiUrlEntry) {
34688
+ return undefined;
34689
+ }
34690
+ return {
34691
+ ...(endpointKey ? { endpointKey } : {}),
34692
+ ...(apiUrlEntry ? { apiUrlEntry } : {}),
34693
+ };
34694
+ }
33947
34695
  buildValue(rawDocument) {
33948
34696
  const document = parseLegacyOrDynamicFormDocument(rawDocument ?? this.createDocumentFromInputs());
33949
34697
  const schemaPrefs = document.contextSnapshot?.schemaPrefs;
@@ -33955,6 +34703,9 @@ class PraxisDynamicFormWidgetConfigEditor {
33955
34703
  inputs: {
33956
34704
  ...(this.inputs ?? {}),
33957
34705
  ...(this.sourceDraft !== undefined ? { configPersistenceStrategy: this.sourceDraft } : {}),
34706
+ ...(this.resourcePathDraft !== undefined
34707
+ ? { resourcePath: this.resourcePathDraft || null }
34708
+ : {}),
33958
34709
  // An explicit layout edit authors sections; merely inspecting generated layout does not.
33959
34710
  ...(layoutChanged && layoutPolicy?.source === 'schema'
33960
34711
  ? { layoutPolicy: { ...layoutPolicy, source: 'authored' } } : {}),
@@ -34002,7 +34753,7 @@ class PraxisDynamicFormWidgetConfigEditor {
34002
34753
  });
34003
34754
  }
34004
34755
  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: `
34756
+ 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
34757
  <section data-testid="dynamic-form-widget-config-editor">
34007
34758
  <div class="configuration-source">
34008
34759
  <mat-form-field appearance="outline" subscriptSizing="dynamic">
@@ -34014,14 +34765,33 @@ class PraxisDynamicFormWidgetConfigEditor {
34014
34765
  <mat-hint>{{ formEditor.tx(configurationSource === 'input-first' ? 'config.general.configurationSource.pageHelp' : 'config.general.configurationSource.preferencesHelp') }}</mat-hint>
34015
34766
  </mat-form-field>
34016
34767
  </div>
34768
+ <div class="resource-quick-connect-host" data-testid="form-widget-resource-quick-connect">
34769
+ <praxis-resource-quick-connect
34770
+ [resourcePath]="effectiveResourcePath"
34771
+ [endpointKey]="effectiveEndpointKey"
34772
+ [apiUrlEntry]="effectiveApiUrlEntry"
34773
+ [intent]="effectiveDiscoveryIntent"
34774
+ (resourcePathChange)="onResourceQuickConnect($event)"
34775
+ ></praxis-resource-quick-connect>
34776
+ </div>
34777
+ @if (schemaWarning) {
34778
+ <p class="form-widget-schema-warning" data-testid="form-widget-schema-warning" role="status">
34779
+ {{ schemaWarning }}
34780
+ </p>
34781
+ }
34017
34782
  <praxis-dynamic-form-config-editor #formEditor />
34018
34783
  <p class="widget-save-hint">{{ formEditor.tx('config.general.pageSaveHint') }}</p>
34019
34784
  </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 });
34785
+ `, 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", "resourceSelected"] }, { 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
34786
  }
34022
34787
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisDynamicFormWidgetConfigEditor, decorators: [{
34023
34788
  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: `
34789
+ args: [{ providers: [{ provide: SETTINGS_PANEL_DATA, useValue: null }], selector: 'praxis-dynamic-form-widget-config-editor', standalone: true, imports: [
34790
+ PraxisDynamicFormConfigEditor,
34791
+ ResourceQuickConnectComponent,
34792
+ MatFormFieldModule,
34793
+ MatSelectModule,
34794
+ ], template: `
34025
34795
  <section data-testid="dynamic-form-widget-config-editor">
34026
34796
  <div class="configuration-source">
34027
34797
  <mat-form-field appearance="outline" subscriptSizing="dynamic">
@@ -34033,10 +34803,24 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
34033
34803
  <mat-hint>{{ formEditor.tx(configurationSource === 'input-first' ? 'config.general.configurationSource.pageHelp' : 'config.general.configurationSource.preferencesHelp') }}</mat-hint>
34034
34804
  </mat-form-field>
34035
34805
  </div>
34806
+ <div class="resource-quick-connect-host" data-testid="form-widget-resource-quick-connect">
34807
+ <praxis-resource-quick-connect
34808
+ [resourcePath]="effectiveResourcePath"
34809
+ [endpointKey]="effectiveEndpointKey"
34810
+ [apiUrlEntry]="effectiveApiUrlEntry"
34811
+ [intent]="effectiveDiscoveryIntent"
34812
+ (resourcePathChange)="onResourceQuickConnect($event)"
34813
+ ></praxis-resource-quick-connect>
34814
+ </div>
34815
+ @if (schemaWarning) {
34816
+ <p class="form-widget-schema-warning" data-testid="form-widget-schema-warning" role="status">
34817
+ {{ schemaWarning }}
34818
+ </p>
34819
+ }
34036
34820
  <praxis-dynamic-form-config-editor #formEditor />
34037
34821
  <p class="widget-save-hint">{{ formEditor.tx('config.general.pageSaveHint') }}</p>
34038
34822
  </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"] }]
34823
+ `, 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
34824
  }], propDecorators: { inputs: [{
34041
34825
  type: Input
34042
34826
  }], widgetKey: [{
@@ -34371,6 +35155,21 @@ const PRAXIS_DYNAMIC_FORM_COMPONENT_METADATA = {
34371
35155
  context: {
34372
35156
  effectiveConfig: materializedInputs?.['config'],
34373
35157
  layoutPolicy: materializedInputs?.['layoutPolicy'],
35158
+ ...(materializedInputs?.['resourcePath'] !== undefined
35159
+ ? { resourcePath: materializedInputs['resourcePath'] }
35160
+ : {}),
35161
+ ...(materializedInputs?.['apiEndpointKey'] !== undefined
35162
+ ? { apiEndpointKey: materializedInputs['apiEndpointKey'] }
35163
+ : {}),
35164
+ ...(materializedInputs?.['endpointKey'] !== undefined
35165
+ ? { endpointKey: materializedInputs['endpointKey'] }
35166
+ : {}),
35167
+ ...(materializedInputs?.['apiUrlEntry'] !== undefined
35168
+ ? { apiUrlEntry: materializedInputs['apiUrlEntry'] }
35169
+ : {}),
35170
+ ...(materializedInputs?.['schemaSource'] !== undefined
35171
+ ? { schemaSource: materializedInputs['schemaSource'] }
35172
+ : {}),
34374
35173
  },
34375
35174
  }),
34376
35175
  title: 'Configurar formulário',