@praxisui/core 9.0.0-beta.40 → 9.0.0-beta.41

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.
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": "1.0.0",
3
- "generatedAt": "2026-07-06T15:31:28.393Z",
3
+ "generatedAt": "2026-07-06T18:00:15.329Z",
4
4
  "packageName": "@praxisui/core",
5
- "packageVersion": "9.0.0-beta.40",
5
+ "packageVersion": "9.0.0-beta.41",
6
6
  "sourceRegistry": "praxis-component-registry-ingestion",
7
7
  "sourceRegistryVersion": "1.0.0",
8
8
  "componentCount": 4,
@@ -13960,7 +13960,7 @@ class SurfaceOpenMaterializerService {
13960
13960
  };
13961
13961
  }
13962
13962
  const schemaFields = await this.resolveSchemaFields(payload, discoveryOptions);
13963
- return this.materializeArrayAsTable(payload, collectionData, schemaFields, resourceId, readUrl);
13963
+ return this.materializeArrayProjection(payload, collectionData, schemaFields, resourceId, readUrl);
13964
13964
  }
13965
13965
  if (responseCardinality === 'COLLECTION' || responseCardinality === 'PAGE') {
13966
13966
  return {
@@ -14133,6 +14133,86 @@ class SurfaceOpenMaterializerService {
14133
14133
  return [];
14134
14134
  }
14135
14135
  }
14136
+ materializeArrayProjection(payload, data, fields, resourceId, readUrl) {
14137
+ if (this.hasRelatedChildWriteOperations(payload)) {
14138
+ return this.materializeArrayAsCrud(payload, data, fields, resourceId, readUrl);
14139
+ }
14140
+ return this.materializeArrayAsTable(payload, data, fields, resourceId, readUrl);
14141
+ }
14142
+ materializeArrayAsCrud(payload, data, fields, resourceId, readUrl) {
14143
+ const previousInputs = payload.widget.inputs || {};
14144
+ const surfaceId = this.stableSurfaceId(payload);
14145
+ const relatedResource = this.resolveRelatedResource(payload);
14146
+ const schemaResourcePath = this.normalizeResourcePath(relatedResource?.childResourcePath || this.resolveSurfacePath(payload));
14147
+ const resourcePath = this.normalizeResourcePath(readUrl);
14148
+ const tableConfig = this.buildLocalTableConfig(fields, data, payload);
14149
+ const operations = this.resolveRelatedChildOperations(payload);
14150
+ const actions = this.buildRelatedCrudActions(payload, operations);
14151
+ return {
14152
+ ...payload,
14153
+ widget: {
14154
+ ...payload.widget,
14155
+ id: 'praxis-crud',
14156
+ bindingOrder: [
14157
+ 'crudId',
14158
+ 'componentInstanceId',
14159
+ 'metadata',
14160
+ 'context',
14161
+ 'enableCustomization',
14162
+ ],
14163
+ outputs: {
14164
+ afterOpen: 'emit',
14165
+ afterClose: 'emit',
14166
+ afterSave: 'emit',
14167
+ afterDelete: 'emit',
14168
+ rowClick: 'emit',
14169
+ selectionChange: 'emit',
14170
+ error: 'emit',
14171
+ },
14172
+ inputs: {
14173
+ crudId: String(previousInputs['tableId'] || surfaceId),
14174
+ componentInstanceId: `${surfaceId}.${resourceId}`,
14175
+ metadata: {
14176
+ component: 'praxis-crud',
14177
+ resource: {
14178
+ path: resourcePath,
14179
+ schemaPath: schemaResourcePath || null,
14180
+ idField: this.resolveRelatedSelectionKeyField(payload),
14181
+ endpointKey: previousInputs['apiEndpointKey'] ?? undefined,
14182
+ apiUrlEntry: previousInputs['apiUrlEntry'] ?? undefined,
14183
+ },
14184
+ data,
14185
+ table: tableConfig,
14186
+ queryContext: previousInputs['queryContext'] ?? payload.context?.['queryContext'] ?? null,
14187
+ defaults: {
14188
+ openMode: 'drawer',
14189
+ modal: {
14190
+ width: 'min(720px, 100vw)',
14191
+ maxWidth: '100vw',
14192
+ height: '100vh',
14193
+ position: 'end',
14194
+ },
14195
+ },
14196
+ actions,
14197
+ },
14198
+ context: {
14199
+ ...(payload.context || {}),
14200
+ materialization: {
14201
+ ...payload.context?.['materialization'],
14202
+ relatedResourceCrud: true,
14203
+ },
14204
+ },
14205
+ enableCustomization: previousInputs['enableCustomization'] ?? true,
14206
+ },
14207
+ },
14208
+ context: this.mergeMaterializationContext(payload, {
14209
+ readUrl,
14210
+ dataShape: 'array',
14211
+ recordCount: data.length,
14212
+ presentation: 'materialized-crud',
14213
+ }),
14214
+ };
14215
+ }
14136
14216
  materializeArrayAsTable(payload, data, fields, resourceId, readUrl) {
14137
14217
  const previousInputs = payload.widget.inputs || {};
14138
14218
  const tableInputOverrides = this.projectTableInputs(previousInputs);
@@ -14169,7 +14249,7 @@ class SurfaceOpenMaterializerService {
14169
14249
  subtitle: payload.subtitle,
14170
14250
  icon: payload.icon,
14171
14251
  data,
14172
- config: this.buildLocalTableConfig(fields, data),
14252
+ config: this.buildLocalTableConfig(fields, data, payload),
14173
14253
  enableCustomization: previousInputs['enableCustomization'] ?? true,
14174
14254
  },
14175
14255
  },
@@ -14245,7 +14325,7 @@ class SurfaceOpenMaterializerService {
14245
14325
  ]);
14246
14326
  return Object.fromEntries(Object.entries(inputs).filter(([key]) => supported.has(key)));
14247
14327
  }
14248
- buildLocalTableConfig(fields, data) {
14328
+ buildLocalTableConfig(fields, data, payload) {
14249
14329
  const schemaColumns = fields
14250
14330
  .filter((field) => !field.hidden && !field.tableHidden && !!field.name)
14251
14331
  .map((field) => ({
@@ -14258,8 +14338,30 @@ class SurfaceOpenMaterializerService {
14258
14338
  const fallbackColumns = schemaColumns.length
14259
14339
  ? []
14260
14340
  : this.inferColumnsFromData(data);
14341
+ const childOperations = this.resolveRelatedChildOperations(payload);
14342
+ const relatedActions = this.buildRelatedCrudActions(payload, childOperations);
14343
+ const toolbarActions = relatedActions
14344
+ .filter((action) => action['action'] === 'create')
14345
+ .map((action) => ({ ...action, variant: 'raised' }));
14346
+ const bulkActions = relatedActions
14347
+ .filter((action) => action['action'] === 'edit' || action['action'] === 'delete');
14261
14348
  return {
14262
14349
  columns: schemaColumns.length ? schemaColumns : fallbackColumns,
14350
+ toolbar: toolbarActions.length
14351
+ ? {
14352
+ visible: true,
14353
+ actions: toolbarActions,
14354
+ }
14355
+ : undefined,
14356
+ actions: bulkActions.length
14357
+ ? {
14358
+ bulk: {
14359
+ enabled: true,
14360
+ position: 'toolbar',
14361
+ actions: bulkActions,
14362
+ },
14363
+ }
14364
+ : undefined,
14263
14365
  behavior: {
14264
14366
  localDataMode: { enabled: true },
14265
14367
  pagination: { enabled: true, pageSize: 10 },
@@ -14271,6 +14373,109 @@ class SurfaceOpenMaterializerService {
14271
14373
  },
14272
14374
  };
14273
14375
  }
14376
+ buildRelatedCrudActions(payload, operations) {
14377
+ const noun = this.resolveRelatedActionNoun(payload);
14378
+ const actions = [];
14379
+ if (operations.has('CREATE')) {
14380
+ actions.push({
14381
+ id: 'create',
14382
+ action: 'create',
14383
+ label: `Novo ${noun}`,
14384
+ icon: 'add',
14385
+ color: 'primary',
14386
+ type: 'button',
14387
+ openMode: 'drawer',
14388
+ });
14389
+ }
14390
+ if (operations.has('UPDATE')) {
14391
+ actions.push({
14392
+ id: 'edit',
14393
+ action: 'edit',
14394
+ label: `Editar ${noun}`,
14395
+ icon: 'edit',
14396
+ color: 'primary',
14397
+ type: 'button',
14398
+ openMode: 'drawer',
14399
+ });
14400
+ }
14401
+ if (operations.has('DELETE')) {
14402
+ actions.push({
14403
+ id: 'delete',
14404
+ action: 'delete',
14405
+ label: `Apagar ${noun}`,
14406
+ icon: 'delete',
14407
+ color: 'warn',
14408
+ type: 'button',
14409
+ openMode: 'drawer',
14410
+ requiresConfirmation: true,
14411
+ });
14412
+ }
14413
+ return actions;
14414
+ }
14415
+ hasRelatedChildWriteOperations(payload) {
14416
+ const operations = this.resolveRelatedChildOperations(payload);
14417
+ return operations.has('CREATE') || operations.has('UPDATE') || operations.has('DELETE');
14418
+ }
14419
+ resolveRelatedChildOperations(payload) {
14420
+ const relatedResource = this.resolveRelatedResource(payload);
14421
+ const operations = Array.isArray(relatedResource?.childOperations)
14422
+ ? relatedResource.childOperations
14423
+ : [];
14424
+ return new Set(operations
14425
+ .map((operation) => String(operation || '').trim().toUpperCase())
14426
+ .filter(Boolean));
14427
+ }
14428
+ resolveRelatedResource(payload) {
14429
+ const relatedResource = payload.context?.['relatedResource'];
14430
+ return relatedResource;
14431
+ }
14432
+ resolveRelatedSelectionKeyField(payload) {
14433
+ const relatedResource = this.resolveRelatedResource(payload);
14434
+ const field = String(relatedResource?.selectionKeyField || '').trim();
14435
+ return field || 'id';
14436
+ }
14437
+ resolveSurfacePath(payload) {
14438
+ const surface = payload.context?.['surface'];
14439
+ return String(surface?.['path'] || '').trim();
14440
+ }
14441
+ normalizeResourcePath(value) {
14442
+ const raw = String(value || '').trim();
14443
+ if (!raw)
14444
+ return '';
14445
+ if (/^https?:\/\//i.test(raw)) {
14446
+ try {
14447
+ return new URL(raw).pathname.replace(/\/+$/, '');
14448
+ }
14449
+ catch {
14450
+ return '';
14451
+ }
14452
+ }
14453
+ return raw.replace(/\/+$/, '');
14454
+ }
14455
+ resolveRelatedActionNoun(payload) {
14456
+ const source = [
14457
+ payload.title,
14458
+ payload.context?.['surface']?.['title'],
14459
+ this.resolveRelatedResource(payload)?.childResourcePath,
14460
+ ]
14461
+ .map((value) => String(value || '').trim())
14462
+ .find(Boolean) || '';
14463
+ const normalized = source
14464
+ .normalize('NFD')
14465
+ .replace(/[\u0300-\u036f]/g, '')
14466
+ .toLowerCase();
14467
+ if (/\b(documentos?|documents?)\b/.test(normalized)) {
14468
+ return 'documento';
14469
+ }
14470
+ const firstToken = normalized
14471
+ .split(/[^a-z0-9]+/)
14472
+ .filter(Boolean)
14473
+ .find((token) => !['de', 'da', 'do', 'das', 'dos', 'legal', 'legais'].includes(token));
14474
+ if (firstToken && firstToken.length > 3) {
14475
+ return firstToken.endsWith('s') ? firstToken.slice(0, -1) : firstToken;
14476
+ }
14477
+ return 'item';
14478
+ }
14274
14479
  inferColumnsFromData(data) {
14275
14480
  const first = data.find((item) => item && typeof item === 'object' && !Array.isArray(item));
14276
14481
  if (!first || typeof first !== 'object')
@@ -14359,6 +14564,7 @@ class CrudOperationResolutionService {
14359
14564
  throw new Error('CrudOperationResolutionService requires resourcePath.');
14360
14565
  }
14361
14566
  const resourcePath = this.resolveCanonicalResourcePath(request, metadataResourcePath);
14567
+ const schemaResourcePath = this.resolveCanonicalSchemaResourcePath(request, resourcePath);
14362
14568
  const diagnostics = [];
14363
14569
  const resolutionOptions = this.toDiscoveryOptions(request);
14364
14570
  if (this.hasExplicitContract(request)) {
@@ -14393,9 +14599,9 @@ class CrudOperationResolutionService {
14393
14599
  visible: this.isAvailable(operationCapability),
14394
14600
  enabled: this.isAvailable(operationCapability),
14395
14601
  availability: operationCapability?.availability ?? null,
14396
- schemaUrl: this.resolveCanonicalSchemaUrl(operation, resourcePath, preferredMethod, resolutionOptions),
14602
+ schemaUrl: this.resolveCanonicalSchemaUrl(operation, schemaResourcePath, preferredMethod, resolutionOptions),
14397
14603
  responseSchemaUrl: operation === 'view'
14398
- ? this.resolveCanonicalSchemaUrl('view', resourcePath, preferredMethod, resolutionOptions)
14604
+ ? this.resolveCanonicalSchemaUrl('view', schemaResourcePath, preferredMethod, resolutionOptions)
14399
14605
  : null,
14400
14606
  submitUrl: linkResolution.href,
14401
14607
  submitMethod: operation === 'view'
@@ -14407,6 +14613,7 @@ class CrudOperationResolutionService {
14407
14613
  return this.buildConventionResolution({
14408
14614
  operation,
14409
14615
  resourcePath,
14616
+ schemaResourcePath,
14410
14617
  resourceId: request.resourceId ?? null,
14411
14618
  diagnostics,
14412
14619
  resolutionOptions,
@@ -14468,7 +14675,7 @@ class CrudOperationResolutionService {
14468
14675
  };
14469
14676
  }
14470
14677
  buildConventionResolution(params) {
14471
- const { operation, resourcePath, resourceId, diagnostics, resolutionOptions, capability, preferredMethod, scope } = params;
14678
+ const { operation, resourcePath, schemaResourcePath, resourceId, diagnostics, resolutionOptions, capability, preferredMethod, scope } = params;
14472
14679
  const submitUrl = this.resolveConventionSubmitUrl(operation, resourcePath, resourceId, diagnostics, resolutionOptions);
14473
14680
  return {
14474
14681
  operation,
@@ -14480,9 +14687,9 @@ class CrudOperationResolutionService {
14480
14687
  visible: this.isAvailable(capability),
14481
14688
  enabled: this.isAvailable(capability),
14482
14689
  availability: capability?.availability ?? null,
14483
- schemaUrl: this.resolveCanonicalSchemaUrl(operation, resourcePath, preferredMethod, resolutionOptions),
14690
+ schemaUrl: this.resolveCanonicalSchemaUrl(operation, schemaResourcePath, preferredMethod, resolutionOptions),
14484
14691
  responseSchemaUrl: operation === 'view'
14485
- ? this.resolveCanonicalSchemaUrl('view', resourcePath, preferredMethod, resolutionOptions)
14692
+ ? this.resolveCanonicalSchemaUrl('view', schemaResourcePath, preferredMethod, resolutionOptions)
14486
14693
  : null,
14487
14694
  submitUrl,
14488
14695
  submitMethod: operation === 'view' ? preferredMethod : preferredMethod?.toLowerCase() ?? null,
@@ -14656,7 +14863,9 @@ class CrudOperationResolutionService {
14656
14863
  }
14657
14864
  try {
14658
14865
  const resolved = this.discovery.resolveHref(normalized, options);
14659
- const pathname = new URL(resolved).pathname.replace(/\/+/g, '/').replace(/\/+$/, '');
14866
+ const pathname = decodeURIComponent(new URL(resolved).pathname)
14867
+ .replace(/\/+/g, '/')
14868
+ .replace(/\/+$/, '');
14660
14869
  return pathname || (normalized.startsWith('/') ? normalized : `/${normalized}`);
14661
14870
  }
14662
14871
  catch {
@@ -14670,6 +14879,10 @@ class CrudOperationResolutionService {
14670
14879
  }
14671
14880
  return fallbackResourcePath;
14672
14881
  }
14882
+ resolveCanonicalSchemaResourcePath(request, fallbackResourcePath) {
14883
+ const explicitSchemaPath = this.normalizeResourcePath(request.schemaResourcePath || '');
14884
+ return explicitSchemaPath || fallbackResourcePath;
14885
+ }
14673
14886
  toDiscoveryOptions(request) {
14674
14887
  if (!request.endpointKey && !request.apiUrlEntry) {
14675
14888
  return undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@praxisui/core",
3
- "version": "9.0.0-beta.40",
3
+ "version": "9.0.0-beta.41",
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": "^21.0.0",
@@ -9759,11 +9759,21 @@ declare class SurfaceOpenMaterializerService {
9759
9759
  private resolveResourceId;
9760
9760
  private resolveItemReadUrl;
9761
9761
  private resolveSchemaFields;
9762
+ private materializeArrayProjection;
9763
+ private materializeArrayAsCrud;
9762
9764
  private materializeArrayAsTable;
9763
9765
  private ensureCollectionTableSelectionContract;
9764
9766
  private ensureTableSelectionConfig;
9765
9767
  private projectTableInputs;
9766
9768
  private buildLocalTableConfig;
9769
+ private buildRelatedCrudActions;
9770
+ private hasRelatedChildWriteOperations;
9771
+ private resolveRelatedChildOperations;
9772
+ private resolveRelatedResource;
9773
+ private resolveRelatedSelectionKeyField;
9774
+ private resolveSurfacePath;
9775
+ private normalizeResourcePath;
9776
+ private resolveRelatedActionNoun;
9767
9777
  private inferColumnsFromData;
9768
9778
  private extractCollectionData;
9769
9779
  private mergeMaterializationContext;
@@ -9801,6 +9811,7 @@ interface ResolvedCrudOperation {
9801
9811
  interface ResolveCrudOperationRequest {
9802
9812
  operation: ResourceCrudOperationId;
9803
9813
  resourcePath: string;
9814
+ schemaResourcePath?: string | null;
9804
9815
  resourceId?: string | number | null;
9805
9816
  explicit?: ExplicitCrudResolutionContract | null;
9806
9817
  }
@@ -9838,6 +9849,7 @@ declare class CrudOperationResolutionService {
9838
9849
  private normalizeResourcePath;
9839
9850
  private resolveSchemaResourcePath;
9840
9851
  private resolveCanonicalResourcePath;
9852
+ private resolveCanonicalSchemaResourcePath;
9841
9853
  private toDiscoveryOptions;
9842
9854
  static ɵfac: i0.ɵɵFactoryDeclaration<CrudOperationResolutionService, never>;
9843
9855
  static ɵprov: i0.ɵɵInjectableDeclaration<CrudOperationResolutionService>;