@praxisui/core 8.0.0-beta.25 → 8.0.0-beta.27

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.
@@ -3307,7 +3307,9 @@ class GenericCrudService {
3307
3307
  // Angular HttpClient treats 304 as error; also handle status 0 (CORS/network) by reusing cache
3308
3308
  catchError((err) => {
3309
3309
  if (err?.status === 404) {
3310
- GenericCrudService.unavailableFilteredSchemaUrls.add(filteredUrl);
3310
+ if (this.shouldRememberFilteredSchemaEndpointUnavailable(err)) {
3311
+ GenericCrudService.unavailableFilteredSchemaUrls.add(filteredUrl);
3312
+ }
3311
3313
  return this.fetchDirectSchema(url, schemaId, apiOrigin, entry, locale, tenant, options?.httpContext);
3312
3314
  }
3313
3315
  if (err?.status === 304 || err?.status === 0) {
@@ -3366,6 +3368,30 @@ class GenericCrudService {
3366
3368
  return this.handleError(err);
3367
3369
  }))), shareReplay(1));
3368
3370
  }
3371
+ shouldRememberFilteredSchemaEndpointUnavailable(err) {
3372
+ const payload = err?.error;
3373
+ if (!payload || typeof payload !== 'object') {
3374
+ return true;
3375
+ }
3376
+ const message = String(payload.message || '').toLowerCase();
3377
+ const errors = Array.isArray(payload.errors)
3378
+ ? (payload.errors || [])
3379
+ : [];
3380
+ const hasFilteredPathMiss = errors.some((item) => {
3381
+ if (!item || typeof item !== 'object')
3382
+ return false;
3383
+ const record = item;
3384
+ const type = String(record['type'] || '').toLowerCase();
3385
+ const category = String(record['category'] || '').toLowerCase();
3386
+ const itemMessage = String(record['message'] || '').toLowerCase();
3387
+ return type.includes('resource-not-found')
3388
+ || category === 'business_logic'
3389
+ || itemMessage.includes('path or operation was not found');
3390
+ });
3391
+ // A structured business miss means /schemas/filtered is alive; only the requested
3392
+ // resource operation was not present in the catalog. Do not poison later resources.
3393
+ return !hasFilteredPathMiss && !message.includes('path or operation was not found');
3394
+ }
3369
3395
  fetchDirectSchema(url, schemaId, apiOrigin, entry, locale, tenant, httpContext) {
3370
3396
  return from(this.ensureSchemaCacheReady()).pipe(concatMap(() => from(this._schemaCache.get(schemaId)).pipe(concatMap((cached) => {
3371
3397
  const baseHeaders = composeHeadersWithVersion(entry);
@@ -15283,6 +15309,11 @@ function stripLocalSubmitSemantics$1(field) {
15283
15309
  void submitPolicy;
15284
15310
  return rest;
15285
15311
  }
15312
+ function stripLocalServerOwnedSemantics$1(field) {
15313
+ const { defaultValue, ...rest } = stripLocalSubmitSemantics$1(field);
15314
+ void defaultValue;
15315
+ return rest;
15316
+ }
15286
15317
  /**
15287
15318
  * Synchronizes local config with server metadata
15288
15319
  * Detects additions, removals, and modifications
@@ -15307,7 +15338,7 @@ function syncWithServerMetadata(localConfig, serverMetadata) {
15307
15338
  return; // handled later
15308
15339
  // Deep-ish merge with special handling for validators/options
15309
15340
  const base = { ...serverField };
15310
- const loc = { ...stripLocalSubmitSemantics$1(localField) };
15341
+ const loc = { ...stripLocalServerOwnedSemantics$1(localField) };
15311
15342
  const mergedValidators = { ...serverField.validators, ...localField.validators };
15312
15343
  // Options/nodes family: keep local options if provided, else server
15313
15344
  const pick = (k) => (loc[k] !== undefined ? loc[k] : base[k]);
@@ -23044,7 +23075,7 @@ class DynamicWidgetLoaderDirective {
23044
23075
  this.tryRender();
23045
23076
  }
23046
23077
  ngOnChanges(changes) {
23047
- if (changes['widget'] || changes['context']) {
23078
+ if (changes['widget'] || changes['context'] || changes['autoWireOutputs']) {
23048
23079
  this.tryRender();
23049
23080
  }
23050
23081
  }
@@ -23076,7 +23107,7 @@ class DynamicWidgetLoaderDirective {
23076
23107
  }
23077
23108
  if (this.compRef) {
23078
23109
  const meta = this.registry.get(def.id);
23079
- const inputs = def.inputs || {};
23110
+ const inputs = this.withInferredIdentityInputs(def.id, def.inputs || {}, def.childWidgetKey);
23080
23111
  const outputs = def.outputs || {};
23081
23112
  try {
23082
23113
  // Valida e ajusta tipos conforme metadata (pode atualizar inputs)
@@ -23205,12 +23236,34 @@ class DynamicWidgetLoaderDirective {
23205
23236
  apply(key, raw);
23206
23237
  }
23207
23238
  }
23239
+ withInferredIdentityInputs(id, inputs, childWidgetKey) {
23240
+ const identityInputByComponent = {
23241
+ 'praxis-table': 'tableId',
23242
+ 'praxis-crud': 'crudId',
23243
+ 'praxis-dynamic-form': 'formId',
23244
+ 'praxis-tabs': 'tabsId',
23245
+ 'praxis-list': 'listId',
23246
+ 'praxis-expansion': 'expansionId',
23247
+ 'praxis-stepper': 'stepperId',
23248
+ 'praxis-files-upload': 'filesUploadId',
23249
+ };
23250
+ const identityInput = identityInputByComponent[id];
23251
+ const fallbackId = String(childWidgetKey || '').trim();
23252
+ if (!identityInput || !fallbackId) {
23253
+ return { ...inputs };
23254
+ }
23255
+ return {
23256
+ ...inputs,
23257
+ [identityInput]: inputs[identityInput] || fallbackId,
23258
+ componentInstanceId: inputs['componentInstanceId'] || fallbackId,
23259
+ };
23260
+ }
23208
23261
  bindOutputs(instance, id, outputs, meta) {
23209
23262
  // Clean existing subscriptions
23210
23263
  this.outputSubs.forEach((u) => u());
23211
23264
  this.outputSubs = [];
23212
23265
  let keys = Object.keys(outputs || {});
23213
- if ((this.autoWireOutputs || keys.length === 0) && meta?.outputs?.length) {
23266
+ if (this.autoWireOutputs && keys.length === 0 && meta?.outputs?.length) {
23214
23267
  keys = meta.outputs.map((o) => o.name);
23215
23268
  }
23216
23269
  if (!keys.includes('widgetEvent') && isSubscribableOutput(instance?.widgetEvent)) {
@@ -28101,7 +28154,7 @@ class DynamicWidgetPageComponent {
28101
28154
  return;
28102
28155
  }
28103
28156
  const page = this.ensurePageDefinition();
28104
- const widgetInputPatchResult = this.applyWidgetInputPatchToPage(page, fromKey, evt?.payload);
28157
+ const widgetInputPatchResult = this.applyWidgetInputPatchToPage(page, fromKey, evt);
28105
28158
  const pageWithPatchedInputs = widgetInputPatchResult.page;
28106
28159
  const cycle = this.dispatchWidgetEventToComposition(pageWithPatchedInputs, fromKey, evt);
28107
28160
  if (!cycle || !cycle.matchedLinkIds.length) {
@@ -28137,7 +28190,8 @@ class DynamicWidgetPageComponent {
28137
28190
  }
28138
28191
  this.applyPageUpdate({ ...pageWithPatchedInputs, widgets, state }, true, nextRuntime, false);
28139
28192
  }
28140
- applyWidgetInputPatchToPage(page, widgetKey, payload) {
28193
+ applyWidgetInputPatchToPage(page, widgetKey, evt) {
28194
+ const payload = evt?.payload;
28141
28195
  const inputPatch = this.extractWidgetInputPatch(payload);
28142
28196
  if (!inputPatch) {
28143
28197
  return { page, changed: false };
@@ -28147,6 +28201,31 @@ class DynamicWidgetPageComponent {
28147
28201
  if (widgetIndex < 0) {
28148
28202
  return { page, changed: false };
28149
28203
  }
28204
+ const nestedPath = this.resolveWidgetInputPatchNestedPath(widgets[widgetIndex], evt);
28205
+ if (nestedPath?.length) {
28206
+ let owner = widgets[widgetIndex];
28207
+ let patchChanged = false;
28208
+ for (const [path, value] of Object.entries(inputPatch)) {
28209
+ const normalizedPath = typeof path === 'string' ? path.trim() : '';
28210
+ if (!normalizedPath) {
28211
+ continue;
28212
+ }
28213
+ const result = this.nestedWidgetAccessor.setNestedWidgetInput(owner, nestedPath, normalizedPath, this.cloneStateValues(value));
28214
+ owner = result.widget;
28215
+ patchChanged = patchChanged || result.changed;
28216
+ }
28217
+ if (!patchChanged) {
28218
+ return { page, changed: false };
28219
+ }
28220
+ widgets[widgetIndex] = owner;
28221
+ return {
28222
+ page: {
28223
+ ...page,
28224
+ widgets,
28225
+ },
28226
+ changed: true,
28227
+ };
28228
+ }
28150
28229
  const currentInputs = this.cloneStateValues(widgets[widgetIndex].definition.inputs || {});
28151
28230
  let nextInputs = currentInputs;
28152
28231
  let patchChanged = false;
@@ -28179,6 +28258,22 @@ class DynamicWidgetPageComponent {
28179
28258
  changed: true,
28180
28259
  };
28181
28260
  }
28261
+ resolveWidgetInputPatchNestedPath(owner, evt) {
28262
+ const nestedPath = normalizeWidgetEventPath(evt, {
28263
+ ownerComponentId: owner.definition.id,
28264
+ });
28265
+ if (nestedPath.length && this.nestedWidgetAccessor.resolveNestedWidget(owner, nestedPath)) {
28266
+ return nestedPath;
28267
+ }
28268
+ const sourceChildWidgetKey = String(evt?.sourceChildWidgetKey || '').trim();
28269
+ if (!sourceChildWidgetKey) {
28270
+ return null;
28271
+ }
28272
+ const sourceComponentId = String(evt?.sourceComponentId || '').trim();
28273
+ const match = this.nestedWidgetAccessor.listNestedWidgets(owner).find((candidate) => candidate.childWidgetKey === sourceChildWidgetKey
28274
+ && (!sourceComponentId || candidate.componentId === sourceComponentId));
28275
+ return match?.nestedPath || null;
28276
+ }
28182
28277
  extractWidgetInputPatch(payload) {
28183
28278
  if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {
28184
28279
  return null;
@@ -29483,8 +29578,20 @@ class DynamicWidgetPageComponent {
29483
29578
  if (this.enableCustomization) {
29484
29579
  return true;
29485
29580
  }
29581
+ if (this.hasCompositionOutputLinks(widget?.key)) {
29582
+ return true;
29583
+ }
29486
29584
  return widget?.definition?.inputs?.['enableCustomization'] === true;
29487
29585
  }
29586
+ hasCompositionOutputLinks(widgetKey) {
29587
+ const normalizedWidgetKey = String(widgetKey || '').trim();
29588
+ if (!normalizedWidgetKey) {
29589
+ return false;
29590
+ }
29591
+ return !!this.ensurePageDefinition().composition?.links?.some((link) => link.from.kind === 'component-port'
29592
+ && link.from.ref.widget === normalizedWidgetKey
29593
+ && link.from.ref.direction === 'output');
29594
+ }
29488
29595
  selectWidget(widgetKey) {
29489
29596
  if (!this.enableCustomization)
29490
29597
  return;
@@ -29497,15 +29604,65 @@ class DynamicWidgetPageComponent {
29497
29604
  this.blockedCanvasWidgetKey = null;
29498
29605
  }
29499
29606
  }
29607
+ selectWidgetFromHostEvent(widgetKey, event) {
29608
+ if (this.shouldPreserveInnerWidgetInteraction(event)) {
29609
+ return;
29610
+ }
29611
+ this.selectWidget(widgetKey);
29612
+ }
29500
29613
  isCanvasWidgetSelected(widgetKey) {
29501
29614
  return this.isWidgetSelected(widgetKey);
29502
29615
  }
29503
29616
  isWidgetSelected(widgetKey) {
29504
29617
  return this.selectedWidgetKey === widgetKey;
29505
29618
  }
29619
+ shouldPreserveInnerWidgetInteraction(event) {
29620
+ if (!this.enableCustomization)
29621
+ return false;
29622
+ const target = event.target;
29623
+ const currentTarget = event.currentTarget;
29624
+ if (!(target instanceof HTMLElement) || !(currentTarget instanceof HTMLElement)) {
29625
+ return false;
29626
+ }
29627
+ if (target === currentTarget) {
29628
+ return false;
29629
+ }
29630
+ if (event.type === 'focusin') {
29631
+ return true;
29632
+ }
29633
+ const shellHeader = target.closest('.pdx-shell-header');
29634
+ if (shellHeader && currentTarget.contains(shellHeader)) {
29635
+ return false;
29636
+ }
29637
+ return !!target.closest([
29638
+ 'button',
29639
+ 'a',
29640
+ 'input',
29641
+ 'select',
29642
+ 'textarea',
29643
+ '[contenteditable="true"]',
29644
+ '[role="button"]',
29645
+ '[role="tab"]',
29646
+ '[role="menuitem"]',
29647
+ '[role="option"]',
29648
+ '[role="checkbox"]',
29649
+ '[role="radio"]',
29650
+ '[role="row"]',
29651
+ '[role="gridcell"]',
29652
+ '[mat-menu-trigger-for]',
29653
+ '.mat-mdc-row',
29654
+ '.mat-mdc-cell',
29655
+ '.mat-mdc-header-cell',
29656
+ '.pdx-widget-context-toolbar',
29657
+ '.pdx-canvas-resize',
29658
+ ].join(','));
29659
+ }
29506
29660
  selectCanvasWidget(widgetKey) {
29507
29661
  this.selectWidget(widgetKey);
29508
29662
  }
29663
+ getPageSnapshot() {
29664
+ return this.clonePageDefinition(this.ensurePageDefinition());
29665
+ }
29509
29666
  isCanvasWidgetBlocked(widgetKey) {
29510
29667
  return this.blockedCanvasWidgetKey === widgetKey;
29511
29668
  }
@@ -30393,8 +30550,8 @@ class DynamicWidgetPageComponent {
30393
30550
  [style.gridColumn]="widgetGridColumn(w)"
30394
30551
  [style.gridRow]="widgetGridRow(w)"
30395
30552
  [style.zIndex]="widgetZIndex(w)"
30396
- (click)="selectWidget(w.key)"
30397
- (focusin)="selectWidget(w.key)"
30553
+ (click)="selectWidgetFromHostEvent(w.key, $event)"
30554
+ (focusin)="selectWidgetFromHostEvent(w.key, $event)"
30398
30555
  >
30399
30556
  @if (enableCustomization) {
30400
30557
  @for (handle of canvasResizeHandles; track handle.id) {
@@ -30521,8 +30678,8 @@ class DynamicWidgetPageComponent {
30521
30678
  "
30522
30679
  [class]="w.renderClassName || w.className || ''"
30523
30680
  [style.gridColumn]="widgetGridColumn(w)"
30524
- (click)="selectWidget(w.key)"
30525
- (focusin)="selectWidget(w.key)"
30681
+ (click)="selectWidgetFromHostEvent(w.key, $event)"
30682
+ (focusin)="selectWidgetFromHostEvent(w.key, $event)"
30526
30683
  >
30527
30684
  <praxis-widget-shell
30528
30685
  [shell]="widgetShellForRender(w)"
@@ -30585,8 +30742,8 @@ class DynamicWidgetPageComponent {
30585
30742
  "
30586
30743
  [class]="widgetClassName(w)"
30587
30744
  [style.gridColumn]="widgetGridColumn(w)"
30588
- (click)="selectWidget(w.key)"
30589
- (focusin)="selectWidget(w.key)"
30745
+ (click)="selectWidgetFromHostEvent(w.key, $event)"
30746
+ (focusin)="selectWidgetFromHostEvent(w.key, $event)"
30590
30747
  >
30591
30748
  <praxis-widget-shell
30592
30749
  [shell]="widgetShellForRender(w)"
@@ -30641,8 +30798,8 @@ class DynamicWidgetPageComponent {
30641
30798
  "
30642
30799
  [class]="widgetClassName(w)"
30643
30800
  [style.gridColumn]="widgetGridColumn(w)"
30644
- (click)="selectWidget(w.key)"
30645
- (focusin)="selectWidget(w.key)"
30801
+ (click)="selectWidgetFromHostEvent(w.key, $event)"
30802
+ (focusin)="selectWidgetFromHostEvent(w.key, $event)"
30646
30803
  >
30647
30804
  <praxis-widget-shell
30648
30805
  [shell]="widgetShellForRender(w)"
@@ -30744,8 +30901,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
30744
30901
  [style.gridColumn]="widgetGridColumn(w)"
30745
30902
  [style.gridRow]="widgetGridRow(w)"
30746
30903
  [style.zIndex]="widgetZIndex(w)"
30747
- (click)="selectWidget(w.key)"
30748
- (focusin)="selectWidget(w.key)"
30904
+ (click)="selectWidgetFromHostEvent(w.key, $event)"
30905
+ (focusin)="selectWidgetFromHostEvent(w.key, $event)"
30749
30906
  >
30750
30907
  @if (enableCustomization) {
30751
30908
  @for (handle of canvasResizeHandles; track handle.id) {
@@ -30872,8 +31029,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
30872
31029
  "
30873
31030
  [class]="w.renderClassName || w.className || ''"
30874
31031
  [style.gridColumn]="widgetGridColumn(w)"
30875
- (click)="selectWidget(w.key)"
30876
- (focusin)="selectWidget(w.key)"
31032
+ (click)="selectWidgetFromHostEvent(w.key, $event)"
31033
+ (focusin)="selectWidgetFromHostEvent(w.key, $event)"
30877
31034
  >
30878
31035
  <praxis-widget-shell
30879
31036
  [shell]="widgetShellForRender(w)"
@@ -30936,8 +31093,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
30936
31093
  "
30937
31094
  [class]="widgetClassName(w)"
30938
31095
  [style.gridColumn]="widgetGridColumn(w)"
30939
- (click)="selectWidget(w.key)"
30940
- (focusin)="selectWidget(w.key)"
31096
+ (click)="selectWidgetFromHostEvent(w.key, $event)"
31097
+ (focusin)="selectWidgetFromHostEvent(w.key, $event)"
30941
31098
  >
30942
31099
  <praxis-widget-shell
30943
31100
  [shell]="widgetShellForRender(w)"
@@ -30992,8 +31149,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
30992
31149
  "
30993
31150
  [class]="widgetClassName(w)"
30994
31151
  [style.gridColumn]="widgetGridColumn(w)"
30995
- (click)="selectWidget(w.key)"
30996
- (focusin)="selectWidget(w.key)"
31152
+ (click)="selectWidgetFromHostEvent(w.key, $event)"
31153
+ (focusin)="selectWidgetFromHostEvent(w.key, $event)"
30997
31154
  >
30998
31155
  <praxis-widget-shell
30999
31156
  [shell]="widgetShellForRender(w)"
@@ -32051,7 +32208,7 @@ function buildBaseFormField(def) {
32051
32208
  }
32052
32209
  function applyLocalCustomizations$1(base, local) {
32053
32210
  const typeChanged = (base.controlType || '') !== (local.controlType || '');
32054
- const serverBackedLocal = stripLocalSubmitSemantics(local);
32211
+ const serverBackedLocal = stripLocalServerOwnedSemantics(local);
32055
32212
  // Server authority on identity/core props
32056
32213
  const authoritative = {
32057
32214
  name: base.name,
@@ -32108,6 +32265,11 @@ function stripLocalSubmitSemantics(field) {
32108
32265
  void submitPolicy;
32109
32266
  return rest;
32110
32267
  }
32268
+ function stripLocalServerOwnedSemantics(field) {
32269
+ const { defaultValue, ...rest } = stripLocalSubmitSemantics(field);
32270
+ void defaultValue;
32271
+ return rest;
32272
+ }
32111
32273
  function isLocalField(field) {
32112
32274
  return field.source === 'local';
32113
32275
  }
package/index.d.ts CHANGED
@@ -4591,6 +4591,7 @@ declare class GenericCrudService<T, ID extends string | number = string | number
4591
4591
  * ```
4592
4592
  */
4593
4593
  getSchema(options?: CrudOperationOptions): Observable<FieldDefinition[]>;
4594
+ private shouldRememberFilteredSchemaEndpointUnavailable;
4594
4595
  private fetchDirectSchema;
4595
4596
  /** Retorna o campo identificador do recurso (ex.: 'id', 'codigo'), quando derivado do schema. */
4596
4597
  getResourceIdField(): string | undefined;
@@ -12816,6 +12817,7 @@ declare class DynamicWidgetLoaderDirective implements OnInit, OnChanges, OnDestr
12816
12817
  private createComponent;
12817
12818
  private destroyCurrent;
12818
12819
  private bindInputs;
12820
+ private withInferredIdentityInputs;
12819
12821
  private bindOutputs;
12820
12822
  private resolveValue;
12821
12823
  private get widgetDefinition();
@@ -13346,6 +13348,7 @@ declare class DynamicWidgetPageComponent implements OnChanges, OnDestroy {
13346
13348
  ngOnChanges(changes: SimpleChanges): void;
13347
13349
  onWidgetEvent(fromKey: string, evt: WidgetEventEnvelope): void;
13348
13350
  private applyWidgetInputPatchToPage;
13351
+ private resolveWidgetInputPatchNestedPath;
13349
13352
  private extractWidgetInputPatch;
13350
13353
  private buildStateRuntime;
13351
13354
  private bootstrapCompositionAdapter;
@@ -13436,10 +13439,14 @@ declare class DynamicWidgetPageComponent implements OnChanges, OnDestroy {
13436
13439
  private resolveDeviceKind;
13437
13440
  isCanvasMode(): boolean;
13438
13441
  shouldAutoWireOutputs(widget: WidgetInstance | RenderedWidgetInstance): boolean;
13442
+ private hasCompositionOutputLinks;
13439
13443
  selectWidget(widgetKey: string): void;
13444
+ selectWidgetFromHostEvent(widgetKey: string, event: Event): void;
13440
13445
  isCanvasWidgetSelected(widgetKey: string): boolean;
13441
13446
  isWidgetSelected(widgetKey: string): boolean;
13447
+ private shouldPreserveInnerWidgetInteraction;
13442
13448
  selectCanvasWidget(widgetKey: string): void;
13449
+ getPageSnapshot(): WidgetPageDefinition;
13443
13450
  isCanvasWidgetBlocked(widgetKey: string): boolean;
13444
13451
  canvasPreviewItem(): WidgetPageCanvasItem | null;
13445
13452
  canvasPreviewGridColumn(): string | null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@praxisui/core",
3
- "version": "8.0.0-beta.25",
3
+ "version": "8.0.0-beta.27",
4
4
  "description": "Core library for Praxis UI Workspace: types, tokens, services and utilities shared across @praxisui/* packages.",
5
5
  "peerDependencies": {
6
6
  "@angular/common": "^20.0.0",