@praxisui/core 9.0.0-beta.37 → 9.0.0-beta.38

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-06T00:01:30.215Z",
3
+ "generatedAt": "2026-07-06T01:44:02.635Z",
4
4
  "packageName": "@praxisui/core",
5
- "packageVersion": "9.0.0-beta.37",
5
+ "packageVersion": "9.0.0-beta.38",
6
6
  "sourceRegistry": "praxis-component-registry-ingestion",
7
7
  "sourceRegistryVersion": "1.0.0",
8
8
  "componentCount": 4,
@@ -3392,6 +3392,36 @@
3392
3392
  "id": "praxis-related-resource-outlet",
3393
3393
  "description": "Runtime genérico para materializar surface.relatedResource como tabela filha governada por metadata.",
3394
3394
  "inputs": [
3395
+ {
3396
+ "name": "surfaceId",
3397
+ "type": "string | null",
3398
+ "required": false
3399
+ },
3400
+ {
3401
+ "name": "surfaceCatalog",
3402
+ "type": "ResourceSurfaceCatalogResponse | null",
3403
+ "required": false
3404
+ },
3405
+ {
3406
+ "name": "discoverySource",
3407
+ "type": "ResourceLinkSource | null",
3408
+ "required": false
3409
+ },
3410
+ {
3411
+ "name": "parentLinks",
3412
+ "type": "RestApiLinks | RestApiResponse<unknown> | null",
3413
+ "required": false
3414
+ },
3415
+ {
3416
+ "name": "apiEndpointKey",
3417
+ "type": "ApiEndpoint | null",
3418
+ "required": false
3419
+ },
3420
+ {
3421
+ "name": "apiUrlEntry",
3422
+ "type": "ApiUrlEntry | null",
3423
+ "required": false
3424
+ },
3395
3425
  {
3396
3426
  "name": "surface",
3397
3427
  "type": "ResourceSurfaceCatalogItem | null",
@@ -3473,9 +3503,9 @@
3473
3503
  {
3474
3504
  "chunkIndex": 0,
3475
3505
  "chunkKind": "summary",
3476
- "content": "Component ID: praxis-related-resource-outlet\nSelector: praxis-related-resource-outlet\nFriendly Name: Related Resource Outlet\nDescription: Runtime genérico para materializar surface.relatedResource como tabela filha governada por metadata.\nLib/Package: @praxisui/core\nTags: resource, surface, related-resource, runtime, metadata-driven\nInputs:\n - surface (ResourceSurfaceCatalogItem | null)\n - parentRecord (Record<string, unknown> | null)\n - parentResourceId (string | number | null)\n - parentResourcePath (string | null)\n - queryContext (RelatedResourceQueryContext | null)\n - mode ('inline' | 'open-action')\n - state (RelatedResourceResolutionState | null)\n - compact (boolean)\n - strictValidation (boolean)\nOutputs:\n - surfaceOpen (SurfaceOpenPayload)\n - widgetEvent (WidgetEventEnvelope)\n - resourceEvent (PraxisResourceEvent)\n",
3506
+ "content": "Component ID: praxis-related-resource-outlet\nSelector: praxis-related-resource-outlet\nFriendly Name: Related Resource Outlet\nDescription: Runtime genérico para materializar surface.relatedResource como tabela filha governada por metadata.\nLib/Package: @praxisui/core\nTags: resource, surface, related-resource, runtime, metadata-driven\nInputs:\n - surfaceId (string | null)\n - surfaceCatalog (ResourceSurfaceCatalogResponse | null)\n - discoverySource (ResourceLinkSource | null)\n - parentLinks (RestApiLinks | RestApiResponse<unknown> | null)\n - apiEndpointKey (ApiEndpoint | null)\n - apiUrlEntry (ApiUrlEntry | null)\n - surface (ResourceSurfaceCatalogItem | null)\n - parentRecord (Record<string, unknown> | null)\n - parentResourceId (string | number | null)\n - parentResourcePath (string | null)\n - queryContext (RelatedResourceQueryContext | null)\n - mode ('inline' | 'open-action')\n - state (RelatedResourceResolutionState | null)\n - compact (boolean)\n - strictValidation (boolean)\nOutputs:\n - surfaceOpen (SurfaceOpenPayload)\n - widgetEvent (WidgetEventEnvelope)\n - resourceEvent (PraxisResourceEvent)\n",
3477
3507
  "sourcePointer": "praxis-ui-angular/projects/praxis-core/src/lib/surfaces/praxis-related-resource-outlet.metadata.ts",
3478
- "contentHash": "e70544431cec6448df159ac05ec1a8ab672b70cf9c60fd76fd01ab849f18d48d",
3508
+ "contentHash": "116c99da68c5132d32f60a7ad56cd1ed8d5cb918111fffbae4e93eaed2097735",
3479
3509
  "sourceKind": "component_definition",
3480
3510
  "sourceId": "praxis-related-resource-outlet",
3481
3511
  "corpusVersion": "1.0.0"
@@ -3483,9 +3513,9 @@
3483
3513
  {
3484
3514
  "chunkIndex": 1,
3485
3515
  "chunkKind": "recipe",
3486
- "content": "{\n \"_comment\": \"Recipe para agentes usarem surface.relatedResource com o runtime canonico praxis-related-resource-outlet.\",\n \"_comment2\": \"O filtro pai-filho deve vir de relatedResource.childParentField; nao duplique a mesma regra em composition.links quando o outlet puder resolver.\",\n \"templateMeta\": {\n \"registryKey\": \"praxis-related-resource-outlet\",\n \"variantId\": \"inline-table-child-resource\",\n \"status\": \"public-example\",\n \"useWhen\": \"Render a related child resource from ResourceSurfaceCatalogItem.relatedResource without hand-writing table filters or host-specific code.\",\n \"examplePrompts\": [\n \"renderize recursos relacionados de uma linha selecionada\",\n \"crie tabela filha a partir de relatedResource\",\n \"mostrar participantes filtrados pela missao selecionada sem composition manual\"\n ]\n },\n \"platformValue\": {\n \"canonicalContract\": \"praxis-metadata-starter publishes ResourceSurfaceCatalogItem.relatedResource in the surfaces catalog.\",\n \"angularRuntime\": \"@praxisui/core resolves relatedResource into a SurfaceOpenPayload and materializes praxis-table through DynamicWidgetLoader.\",\n \"migrationValue\": \"Hosts keep only parentRecord or parentResourceId in the page; parent-child filtering is declared once in metadata.\",\n \"agentCorpus\": \"This recipe keeps parentResourceKey, childResourceKey, childResourcePath and childParentField explicit so agents do not infer relationships from labels.\"\n },\n \"authoringGuidance\": [\n \"Use praxis-related-resource-outlet when a catalog surface already includes relatedResource.\",\n \"Pass parentRecord when the selected row is available; pass parentResourceId when only the id is available.\",\n \"Do not repeat childParentField filters in composition.links unless the page intentionally overrides queryContext.\",\n \"Keep availability fail-closed; permission-limited and not-found states should render as outlet states instead of empty tables.\",\n \"Use mode=open-action when the host owns drawer/dialog orchestration; use mode=inline for embedded child tables.\"\n ],\n \"teaches\": [\n \"surface.relatedResource\",\n \"praxis-related-resource-outlet\",\n \"related-resource.childParentField\",\n \"related-resource.childResourcePath\",\n \"resourceEvent\",\n \"surfaceOpen\"\n ],\n \"page\": {\n \"layout\": {\n \"orientation\": \"columns\",\n \"columns\": 2,\n \"gap\": \"16px\"\n },\n \"widgets\": [\n {\n \"key\": \"missionParticipants\",\n \"shell\": {\n \"kind\": \"dashboard-card\",\n \"icon\": \"hub\",\n \"title\": \"Related participants\",\n \"subtitle\": \"Child table resolved from surface.relatedResource\"\n },\n \"definition\": {\n \"id\": \"praxis-related-resource-outlet\",\n \"bindingOrder\": [\n \"surface\",\n \"parentRecord\",\n \"parentResourcePath\",\n \"mode\"\n ],\n \"inputs\": {\n \"mode\": \"inline\",\n \"parentResourcePath\": \"operations/missoes\",\n \"parentRecord\": {\n \"id\": 1,\n \"titulo\": \"Atlantic Shield\",\n \"status\": \"PLANEJADA\"\n },\n \"surface\": {\n \"id\": \"team\",\n \"resourceKey\": \"operations.missoes\",\n \"kind\": \"READ_PROJECTION\",\n \"scope\": \"ITEM\",\n \"title\": \"Equipe da missão\",\n \"description\": \"Lista participantes, papéis e resultados associados à missão\",\n \"operationId\": \"getTeam\",\n \"path\": \"/api/operations/missoes/{id}/team\",\n \"method\": \"GET\",\n \"schemaId\": \"missao-team-response\",\n \"schemaUrl\": \"/schemas/filtered?path=%2Fapi%2Foperations%2Fmissoes%2F%7Bid%7D%2Fteam&operation=get&schemaType=response\",\n \"responseCardinality\": \"COLLECTION\",\n \"availability\": {\n \"allowed\": true,\n \"metadata\": {}\n },\n \"order\": 50,\n \"tags\": [\n \"mission\",\n \"team\",\n \"read-projection\",\n \"related-resource\"\n ],\n \"relatedResource\": {\n \"parentResourceKey\": \"operations.missoes\",\n \"parentIdPathVariable\": \"id\",\n \"childResourceKey\": \"operations.missao-participantes\",\n \"childResourcePath\": \"/api/operations/missao-participantes\",\n \"childParentField\": \"missaoId\",\n \"selectable\": true,\n \"selectionKeyField\": \"id\",\n \"childOperations\": [\n \"FILTER\",\n \"LIST\",\n \"CREATE\",\n \"UPDATE\",\n \"DELETE\"\n ]\n }\n }\n },\n \"outputs\": {\n \"widgetEvent\": \"emit\",\n \"resourceEvent\": \"emit\",\n \"surfaceOpen\": \"emit\"\n }\n }\n }\n ]\n }\n}\n",
3516
+ "content": "{\n \"_comment\": \"Recipe para agentes usarem surface.relatedResource com o runtime canonico praxis-related-resource-outlet.\",\n \"_comment2\": \"O filtro pai-filho deve vir de relatedResource.childParentField; nao duplique a mesma regra em composition.links quando o outlet puder resolver.\",\n \"templateMeta\": {\n \"registryKey\": \"praxis-related-resource-outlet\",\n \"variantId\": \"inline-table-child-resource\",\n \"status\": \"public-example\",\n \"useWhen\": \"Render a related child resource from surfaceId or ResourceSurfaceCatalogItem.relatedResource without hand-writing table filters or host-specific code.\",\n \"examplePrompts\": [\n \"renderize recursos relacionados de uma linha selecionada\",\n \"crie tabela filha a partir de relatedResource\",\n \"mostrar participantes filtrados pela missao selecionada sem composition manual\"\n ]\n },\n \"platformValue\": {\n \"canonicalContract\": \"praxis-metadata-starter publishes ResourceSurfaceCatalogItem.relatedResource in the surfaces catalog.\",\n \"angularRuntime\": \"@praxisui/core discovers the requested surface by surfaceId when needed, resolves relatedResource into a SurfaceOpenPayload and materializes praxis-table through DynamicWidgetLoader.\",\n \"migrationValue\": \"Hosts keep only surfaceId plus parentRecord or parentResourceId in the page; parent-child filtering is declared once in metadata.\",\n \"agentCorpus\": \"This recipe keeps surfaceId, parentResourcePath, parentResourceId and parentRecord explicit so agents do not infer relationships from labels.\"\n },\n \"authoringGuidance\": [\n \"Use praxis-related-resource-outlet with surfaceId when the parent resource publishes item-level surfaces.\",\n \"Pass surface directly only when the host already has the ResourceSurfaceCatalogItem available.\",\n \"Pass parentRecord when the selected row is available; pass parentResourceId when only the id is available.\",\n \"Do not repeat childParentField filters in composition.links unless the page intentionally overrides queryContext.\",\n \"Keep availability fail-closed; permission-limited and not-found states should render as outlet states instead of empty tables.\",\n \"Use mode=open-action when the host owns drawer/dialog orchestration; use mode=inline for embedded child tables.\"\n ],\n \"teaches\": [\n \"surface.relatedResource\",\n \"surfaceId\",\n \"praxis-related-resource-outlet\",\n \"related-resource.childParentField\",\n \"related-resource.childResourcePath\",\n \"resourceEvent\",\n \"surfaceOpen\"\n ],\n \"page\": {\n \"layout\": {\n \"orientation\": \"columns\",\n \"columns\": 2,\n \"gap\": \"16px\"\n },\n \"widgets\": [\n {\n \"key\": \"missionParticipants\",\n \"shell\": {\n \"kind\": \"dashboard-card\",\n \"icon\": \"hub\",\n \"title\": \"Related participants\",\n \"subtitle\": \"Child table resolved from surface.relatedResource\"\n },\n \"definition\": {\n \"id\": \"praxis-related-resource-outlet\",\n \"bindingOrder\": [\n \"surfaceId\",\n \"parentRecord\",\n \"parentResourceId\",\n \"parentResourcePath\",\n \"mode\"\n ],\n \"inputs\": {\n \"mode\": \"inline\",\n \"surfaceId\": \"team\",\n \"parentResourcePath\": \"operations/missoes\",\n \"parentResourceId\": 1,\n \"parentRecord\": {\n \"id\": 1,\n \"titulo\": \"Atlantic Shield\",\n \"status\": \"PLANEJADA\"\n }\n },\n \"outputs\": {\n \"widgetEvent\": \"emit\",\n \"resourceEvent\": \"emit\",\n \"surfaceOpen\": \"emit\"\n }\n }\n }\n ]\n }\n}\n",
3487
3517
  "sourcePointer": "praxis-ui-angular/examples/ai-recipes/praxis-related-resource-outlet.json",
3488
- "contentHash": "e1c32c7df8fe6f70e0987eacb3215f61ba4170ed2f2059fe3b4113c9d3e9db29",
3518
+ "contentHash": "e9e842635ad7211ff2f021ebbcc1071a8a491cd5c88885525ba153a8a49223b8",
3489
3519
  "sourceKind": "component_definition",
3490
3520
  "sourceId": "praxis-related-resource-outlet",
3491
3521
  "corpusVersion": "1.0.0"
@@ -1,5 +1,5 @@
1
1
  import * as i0 from '@angular/core';
2
- import { Component, InjectionToken, Injectable, inject, Inject, Optional, makeEnvironmentProviders, APP_INITIALIZER, signal, computed, DestroyRef, ENVIRONMENT_INITIALIZER, ErrorHandler, EventEmitter, Output, Input, ChangeDetectionStrategy, Directive, SecurityContext, ViewContainerRef, SimpleChange, ContentChild, HostBinding, HostListener, ViewChildren, ViewChild, input, output } from '@angular/core';
2
+ import { Component, InjectionToken, Injectable, inject, Inject, Optional, makeEnvironmentProviders, APP_INITIALIZER, signal, computed, DestroyRef, ENVIRONMENT_INITIALIZER, ErrorHandler, EventEmitter, Output, Input, ChangeDetectionStrategy, Directive, SecurityContext, ViewContainerRef, SimpleChange, ContentChild, HostBinding, HostListener, ViewChildren, ViewChild, Injector, input, output, effect } from '@angular/core';
3
3
  import * as i1 from '@angular/common/http';
4
4
  import { HttpHeaders, HttpClient, HttpParams, HttpResponse, HttpContextToken, HTTP_INTERCEPTORS, withInterceptors } from '@angular/common/http';
5
5
  import { of, defer, throwError, from, EMPTY, BehaviorSubject, firstValueFrom, Subject, map as map$1, switchMap as switchMap$1 } from 'rxjs';
@@ -36770,7 +36770,16 @@ const RELATED_RESOURCE_OUTLET_I18N_CONFIG = {
36770
36770
  class PraxisRelatedResourceOutletComponent {
36771
36771
  resolver = inject(RelatedResourceSurfaceResolverService);
36772
36772
  i18n = inject(PraxisI18nService);
36773
+ injector = inject(Injector);
36774
+ discoverySubscription;
36775
+ discoveryRequestKey = '';
36773
36776
  surface = input(null, ...(ngDevMode ? [{ debugName: "surface" }] : /* istanbul ignore next */ []));
36777
+ surfaceId = input(null, ...(ngDevMode ? [{ debugName: "surfaceId" }] : /* istanbul ignore next */ []));
36778
+ surfaceCatalog = input(null, ...(ngDevMode ? [{ debugName: "surfaceCatalog" }] : /* istanbul ignore next */ []));
36779
+ discoverySource = input(null, ...(ngDevMode ? [{ debugName: "discoverySource" }] : /* istanbul ignore next */ []));
36780
+ parentLinks = input(null, ...(ngDevMode ? [{ debugName: "parentLinks" }] : /* istanbul ignore next */ []));
36781
+ apiEndpointKey = input(null, ...(ngDevMode ? [{ debugName: "apiEndpointKey" }] : /* istanbul ignore next */ []));
36782
+ apiUrlEntry = input(null, ...(ngDevMode ? [{ debugName: "apiUrlEntry" }] : /* istanbul ignore next */ []));
36774
36783
  parentRecord = input(null, ...(ngDevMode ? [{ debugName: "parentRecord" }] : /* istanbul ignore next */ []));
36775
36784
  parentResourceId = input(null, ...(ngDevMode ? [{ debugName: "parentResourceId" }] : /* istanbul ignore next */ []));
36776
36785
  parentResourcePath = input(null, ...(ngDevMode ? [{ debugName: "parentResourcePath" }] : /* istanbul ignore next */ []));
@@ -36789,13 +36798,20 @@ class PraxisRelatedResourceOutletComponent {
36789
36798
  surfaceOpen = output();
36790
36799
  widgetEvent = output();
36791
36800
  resourceEvent = output();
36801
+ discoveredSurface = signal(null, ...(ngDevMode ? [{ debugName: "discoveredSurface" }] : /* istanbul ignore next */ []));
36802
+ discoveryState = signal(null, ...(ngDevMode ? [{ debugName: "discoveryState" }] : /* istanbul ignore next */ []));
36803
+ discoveryStateReason = signal(null, ...(ngDevMode ? [{ debugName: "discoveryStateReason" }] : /* istanbul ignore next */ []));
36792
36804
  resolution = computed(() => {
36793
36805
  const state = this.state();
36794
36806
  if (state && state !== 'ready') {
36795
36807
  return this.resolver.state(state, this.stateReason() || undefined);
36796
36808
  }
36809
+ const discoveryState = this.discoveryState();
36810
+ if (discoveryState && discoveryState !== 'ready') {
36811
+ return this.resolver.state(discoveryState, this.discoveryStateReason() || undefined);
36812
+ }
36797
36813
  return this.resolver.resolve({
36798
- surface: this.surface(),
36814
+ surface: this.surface() || this.discoveredSurface(),
36799
36815
  parentRecord: this.parentRecord(),
36800
36816
  parentResourceId: this.parentResourceId(),
36801
36817
  parentResourcePath: this.parentResourcePath(),
@@ -36811,6 +36827,67 @@ class PraxisRelatedResourceOutletComponent {
36811
36827
  const state = this.resolution().state;
36812
36828
  return state === 'resolving' || state === 'loading';
36813
36829
  }, ...(ngDevMode ? [{ debugName: "isBusy" }] : /* istanbul ignore next */ []));
36830
+ constructor() {
36831
+ effect((onCleanup) => {
36832
+ const surface = this.surface();
36833
+ const surfaceId = this.surfaceId();
36834
+ const catalog = this.surfaceCatalog();
36835
+ const discoverySource = this.discoverySource() || this.parentLinks();
36836
+ const parentResourcePath = this.parentResourcePath();
36837
+ const parentResourceId = this.parentResourceId();
36838
+ const apiEndpointKey = this.apiEndpointKey();
36839
+ const apiUrlEntry = this.apiUrlEntry();
36840
+ this.discoverySubscription?.unsubscribe();
36841
+ this.discoverySubscription = undefined;
36842
+ if (surface || !this.trim(surfaceId)) {
36843
+ this.resetDiscoveryState();
36844
+ return;
36845
+ }
36846
+ if (catalog) {
36847
+ this.applyCatalogSurface(surfaceId, catalog);
36848
+ return;
36849
+ }
36850
+ const href = this.resolveFallbackSurfaceCatalogHref(parentResourcePath, parentResourceId);
36851
+ if (!discoverySource && !href) {
36852
+ this.discoveredSurface.set(null);
36853
+ this.discoveryState.set('not-found');
36854
+ this.discoveryStateReason.set('surface-discovery-source-not-provided');
36855
+ return;
36856
+ }
36857
+ const options = this.discoveryOptions(apiEndpointKey, apiUrlEntry);
36858
+ const requestKey = this.buildDiscoveryRequestKey(surfaceId, discoverySource, href, options);
36859
+ this.discoveryRequestKey = requestKey;
36860
+ this.discoveredSurface.set(null);
36861
+ this.discoveryState.set('resolving');
36862
+ this.discoveryStateReason.set(null);
36863
+ const discovery = this.injector.get(ResourceDiscoveryService);
36864
+ const catalog$ = discoverySource
36865
+ ? discovery.getSurfaces(discoverySource, options)
36866
+ : discovery.fetchJson(href, options);
36867
+ this.discoverySubscription = catalog$.subscribe({
36868
+ next: (response) => {
36869
+ if (this.discoveryRequestKey !== requestKey) {
36870
+ return;
36871
+ }
36872
+ this.applyCatalogSurface(surfaceId, response);
36873
+ },
36874
+ error: (error) => {
36875
+ if (this.discoveryRequestKey !== requestKey) {
36876
+ return;
36877
+ }
36878
+ this.discoveredSurface.set(null);
36879
+ this.discoveryState.set(this.isPermissionError(error) ? 'permission-limited' : 'error');
36880
+ this.discoveryStateReason.set(this.isPermissionError(error)
36881
+ ? 'surface-discovery-permission-limited'
36882
+ : 'surface-discovery-failed');
36883
+ },
36884
+ });
36885
+ onCleanup(() => {
36886
+ this.discoverySubscription?.unsubscribe();
36887
+ this.discoverySubscription = undefined;
36888
+ });
36889
+ });
36890
+ }
36814
36891
  openRelated() {
36815
36892
  const payload = this.resolution().payload;
36816
36893
  if (payload) {
@@ -36861,6 +36938,65 @@ class PraxisRelatedResourceOutletComponent {
36861
36938
  defaultDescription(state) {
36862
36939
  return this.resolution().reason || state;
36863
36940
  }
36941
+ applyCatalogSurface(surfaceId, catalog) {
36942
+ const normalizedId = this.trim(surfaceId);
36943
+ const surface = (catalog?.surfaces || []).find((candidate) => this.trim(candidate.id) === normalizedId) || null;
36944
+ this.discoveredSurface.set(surface);
36945
+ this.discoveryState.set(surface ? null : 'not-found');
36946
+ this.discoveryStateReason.set(surface ? null : 'surface-id-not-found');
36947
+ }
36948
+ resetDiscoveryState() {
36949
+ this.discoveredSurface.set(null);
36950
+ this.discoveryState.set(null);
36951
+ this.discoveryStateReason.set(null);
36952
+ this.discoveryRequestKey = '';
36953
+ }
36954
+ discoveryOptions(endpointKey, apiUrlEntry) {
36955
+ if (!endpointKey && !apiUrlEntry) {
36956
+ return undefined;
36957
+ }
36958
+ return {
36959
+ endpointKey: endpointKey || undefined,
36960
+ apiUrlEntry: apiUrlEntry || null,
36961
+ };
36962
+ }
36963
+ resolveFallbackSurfaceCatalogHref(parentResourcePath, parentResourceId) {
36964
+ const path = this.normalizeResourcePath(parentResourcePath || '');
36965
+ if (!path || parentResourceId == null || parentResourceId === '') {
36966
+ return null;
36967
+ }
36968
+ return `/api/${path}/${encodeURIComponent(String(parentResourceId))}/surfaces`;
36969
+ }
36970
+ normalizeResourcePath(resourcePath) {
36971
+ let normalized = this.trim(resourcePath);
36972
+ if (/^https?:\/\//i.test(normalized)) {
36973
+ try {
36974
+ normalized = new URL(normalized).pathname;
36975
+ }
36976
+ catch {
36977
+ return '';
36978
+ }
36979
+ }
36980
+ return normalized
36981
+ .replace(/^\/+/, '')
36982
+ .replace(/^(?:api\/)+/i, '')
36983
+ .replace(/\/+$/, '');
36984
+ }
36985
+ buildDiscoveryRequestKey(surfaceId, source, href, options) {
36986
+ return JSON.stringify({
36987
+ surfaceId: this.trim(surfaceId),
36988
+ source,
36989
+ href,
36990
+ endpointKey: options?.endpointKey || null,
36991
+ apiUrlEntry: options?.apiUrlEntry || null,
36992
+ });
36993
+ }
36994
+ isPermissionError(error) {
36995
+ const status = typeof error === 'object' && error
36996
+ ? error.status
36997
+ : null;
36998
+ return status === 401 || status === 403;
36999
+ }
36864
37000
  extractResourceEvent(event) {
36865
37001
  if (event.output !== 'resourceEvent' || !event.payload || typeof event.payload !== 'object') {
36866
37002
  return null;
@@ -36872,8 +37008,11 @@ class PraxisRelatedResourceOutletComponent {
36872
37008
  ? candidate
36873
37009
  : null;
36874
37010
  }
37011
+ trim(value) {
37012
+ return typeof value === 'string' ? value.trim() : '';
37013
+ }
36875
37014
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: PraxisRelatedResourceOutletComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
36876
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: PraxisRelatedResourceOutletComponent, isStandalone: true, selector: "praxis-related-resource-outlet", inputs: { surface: { classPropertyName: "surface", publicName: "surface", isSignal: true, isRequired: false, transformFunction: null }, parentRecord: { classPropertyName: "parentRecord", publicName: "parentRecord", isSignal: true, isRequired: false, transformFunction: null }, parentResourceId: { classPropertyName: "parentResourceId", publicName: "parentResourceId", isSignal: true, isRequired: false, transformFunction: null }, parentResourcePath: { classPropertyName: "parentResourcePath", publicName: "parentResourcePath", isSignal: true, isRequired: false, transformFunction: null }, presentation: { classPropertyName: "presentation", publicName: "presentation", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, subtitle: { classPropertyName: "subtitle", publicName: "subtitle", isSignal: true, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, tableId: { classPropertyName: "tableId", publicName: "tableId", isSignal: true, isRequired: false, transformFunction: null }, queryContext: { classPropertyName: "queryContext", publicName: "queryContext", isSignal: true, isRequired: false, transformFunction: null }, mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null }, state: { classPropertyName: "state", publicName: "state", isSignal: true, isRequired: false, transformFunction: null }, stateReason: { classPropertyName: "stateReason", publicName: "stateReason", isSignal: true, isRequired: false, transformFunction: null }, compact: { classPropertyName: "compact", publicName: "compact", isSignal: true, isRequired: false, transformFunction: null }, strictValidation: { classPropertyName: "strictValidation", publicName: "strictValidation", isSignal: true, isRequired: false, transformFunction: null }, ownerWidgetKey: { classPropertyName: "ownerWidgetKey", publicName: "ownerWidgetKey", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { surfaceOpen: "surfaceOpen", widgetEvent: "widgetEvent", resourceEvent: "resourceEvent" }, host: { attributes: { "data-praxis-related-resource-outlet": "core" }, properties: { "attr.data-state": "resolution().state" } }, providers: [providePraxisI18nConfig(RELATED_RESOURCE_OUTLET_I18N_CONFIG)], ngImport: i0, template: `
37015
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.14", type: PraxisRelatedResourceOutletComponent, isStandalone: true, selector: "praxis-related-resource-outlet", inputs: { surface: { classPropertyName: "surface", publicName: "surface", isSignal: true, isRequired: false, transformFunction: null }, surfaceId: { classPropertyName: "surfaceId", publicName: "surfaceId", isSignal: true, isRequired: false, transformFunction: null }, surfaceCatalog: { classPropertyName: "surfaceCatalog", publicName: "surfaceCatalog", isSignal: true, isRequired: false, transformFunction: null }, discoverySource: { classPropertyName: "discoverySource", publicName: "discoverySource", isSignal: true, isRequired: false, transformFunction: null }, parentLinks: { classPropertyName: "parentLinks", publicName: "parentLinks", isSignal: true, isRequired: false, transformFunction: null }, apiEndpointKey: { classPropertyName: "apiEndpointKey", publicName: "apiEndpointKey", isSignal: true, isRequired: false, transformFunction: null }, apiUrlEntry: { classPropertyName: "apiUrlEntry", publicName: "apiUrlEntry", isSignal: true, isRequired: false, transformFunction: null }, parentRecord: { classPropertyName: "parentRecord", publicName: "parentRecord", isSignal: true, isRequired: false, transformFunction: null }, parentResourceId: { classPropertyName: "parentResourceId", publicName: "parentResourceId", isSignal: true, isRequired: false, transformFunction: null }, parentResourcePath: { classPropertyName: "parentResourcePath", publicName: "parentResourcePath", isSignal: true, isRequired: false, transformFunction: null }, presentation: { classPropertyName: "presentation", publicName: "presentation", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, subtitle: { classPropertyName: "subtitle", publicName: "subtitle", isSignal: true, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, tableId: { classPropertyName: "tableId", publicName: "tableId", isSignal: true, isRequired: false, transformFunction: null }, queryContext: { classPropertyName: "queryContext", publicName: "queryContext", isSignal: true, isRequired: false, transformFunction: null }, mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null }, state: { classPropertyName: "state", publicName: "state", isSignal: true, isRequired: false, transformFunction: null }, stateReason: { classPropertyName: "stateReason", publicName: "stateReason", isSignal: true, isRequired: false, transformFunction: null }, compact: { classPropertyName: "compact", publicName: "compact", isSignal: true, isRequired: false, transformFunction: null }, strictValidation: { classPropertyName: "strictValidation", publicName: "strictValidation", isSignal: true, isRequired: false, transformFunction: null }, ownerWidgetKey: { classPropertyName: "ownerWidgetKey", publicName: "ownerWidgetKey", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { surfaceOpen: "surfaceOpen", widgetEvent: "widgetEvent", resourceEvent: "resourceEvent" }, host: { attributes: { "data-praxis-related-resource-outlet": "core" }, properties: { "attr.data-state": "resolution().state" } }, providers: [providePraxisI18nConfig(RELATED_RESOURCE_OUTLET_I18N_CONFIG)], ngImport: i0, template: `
36877
37016
  <section class="pdx-related-outlet" [class.pdx-related-outlet--compact]="compact()">
36878
37017
  @if (resolution().state === 'ready' && mode() === 'inline' && resolution().payload?.widget) {
36879
37018
  <ng-container
@@ -36964,7 +37103,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
36964
37103
  }
36965
37104
  </section>
36966
37105
  `, styles: [":host{display:block;min-width:0}.pdx-related-outlet{display:block;min-width:0;color:var(--md-sys-color-on-surface, currentColor)}.pdx-related-outlet__state{display:grid;grid-template-columns:auto minmax(0,1fr) auto;align-items:center;gap:var(--pdx-related-outlet-gap, 12px);min-height:var(--pdx-related-outlet-min-height, 64px);padding:var(--pdx-related-outlet-padding, 12px);border:1px solid var(--md-sys-color-outline-variant, rgba(0, 0, 0, .16));border-radius:var(--pdx-related-outlet-radius, 8px);background:var(--md-sys-color-surface-container-low, var(--md-sys-color-surface, transparent))}.pdx-related-outlet--compact .pdx-related-outlet__state{min-height:var(--pdx-related-outlet-compact-min-height, 48px);padding:var(--pdx-related-outlet-compact-padding, 8px 10px)}.pdx-related-outlet__state--ready{border-color:var(--md-sys-color-primary, currentColor)}.pdx-related-outlet__icon{display:inline-grid;place-items:center;width:32px;height:32px;color:var(--md-sys-color-primary, currentColor)}.pdx-related-outlet__state--busy .pdx-related-outlet__icon{color:var(--md-sys-color-secondary, currentColor)}.pdx-related-outlet__copy{display:grid;gap:2px;min-width:0}.pdx-related-outlet__copy h3,.pdx-related-outlet__copy p{margin:0}.pdx-related-outlet__copy h3{font:var(--md-sys-typescale-title-small, 600 .95rem/1.25rem system-ui);color:var(--md-sys-color-on-surface, currentColor)}.pdx-related-outlet__copy p{font:var(--md-sys-typescale-body-small, 400 .82rem/1.15rem system-ui);color:var(--md-sys-color-on-surface-variant, currentColor)}@media(max-width:600px){.pdx-related-outlet__state{grid-template-columns:auto minmax(0,1fr)}.pdx-related-outlet__state button{grid-column:1 / -1;justify-self:start}}\n"] }]
36967
- }], propDecorators: { surface: [{ type: i0.Input, args: [{ isSignal: true, alias: "surface", required: false }] }], parentRecord: [{ type: i0.Input, args: [{ isSignal: true, alias: "parentRecord", required: false }] }], parentResourceId: [{ type: i0.Input, args: [{ isSignal: true, alias: "parentResourceId", required: false }] }], parentResourcePath: [{ type: i0.Input, args: [{ isSignal: true, alias: "parentResourcePath", required: false }] }], presentation: [{ type: i0.Input, args: [{ isSignal: true, alias: "presentation", required: false }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], subtitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "subtitle", required: false }] }], icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], tableId: [{ type: i0.Input, args: [{ isSignal: true, alias: "tableId", required: false }] }], queryContext: [{ type: i0.Input, args: [{ isSignal: true, alias: "queryContext", required: false }] }], mode: [{ type: i0.Input, args: [{ isSignal: true, alias: "mode", required: false }] }], state: [{ type: i0.Input, args: [{ isSignal: true, alias: "state", required: false }] }], stateReason: [{ type: i0.Input, args: [{ isSignal: true, alias: "stateReason", required: false }] }], compact: [{ type: i0.Input, args: [{ isSignal: true, alias: "compact", required: false }] }], strictValidation: [{ type: i0.Input, args: [{ isSignal: true, alias: "strictValidation", required: false }] }], ownerWidgetKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "ownerWidgetKey", required: false }] }], surfaceOpen: [{ type: i0.Output, args: ["surfaceOpen"] }], widgetEvent: [{ type: i0.Output, args: ["widgetEvent"] }], resourceEvent: [{ type: i0.Output, args: ["resourceEvent"] }] } });
37106
+ }], ctorParameters: () => [], propDecorators: { surface: [{ type: i0.Input, args: [{ isSignal: true, alias: "surface", required: false }] }], surfaceId: [{ type: i0.Input, args: [{ isSignal: true, alias: "surfaceId", required: false }] }], surfaceCatalog: [{ type: i0.Input, args: [{ isSignal: true, alias: "surfaceCatalog", required: false }] }], discoverySource: [{ type: i0.Input, args: [{ isSignal: true, alias: "discoverySource", required: false }] }], parentLinks: [{ type: i0.Input, args: [{ isSignal: true, alias: "parentLinks", required: false }] }], apiEndpointKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "apiEndpointKey", required: false }] }], apiUrlEntry: [{ type: i0.Input, args: [{ isSignal: true, alias: "apiUrlEntry", required: false }] }], parentRecord: [{ type: i0.Input, args: [{ isSignal: true, alias: "parentRecord", required: false }] }], parentResourceId: [{ type: i0.Input, args: [{ isSignal: true, alias: "parentResourceId", required: false }] }], parentResourcePath: [{ type: i0.Input, args: [{ isSignal: true, alias: "parentResourcePath", required: false }] }], presentation: [{ type: i0.Input, args: [{ isSignal: true, alias: "presentation", required: false }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], subtitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "subtitle", required: false }] }], icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], tableId: [{ type: i0.Input, args: [{ isSignal: true, alias: "tableId", required: false }] }], queryContext: [{ type: i0.Input, args: [{ isSignal: true, alias: "queryContext", required: false }] }], mode: [{ type: i0.Input, args: [{ isSignal: true, alias: "mode", required: false }] }], state: [{ type: i0.Input, args: [{ isSignal: true, alias: "state", required: false }] }], stateReason: [{ type: i0.Input, args: [{ isSignal: true, alias: "stateReason", required: false }] }], compact: [{ type: i0.Input, args: [{ isSignal: true, alias: "compact", required: false }] }], strictValidation: [{ type: i0.Input, args: [{ isSignal: true, alias: "strictValidation", required: false }] }], ownerWidgetKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "ownerWidgetKey", required: false }] }], surfaceOpen: [{ type: i0.Output, args: ["surfaceOpen"] }], widgetEvent: [{ type: i0.Output, args: ["widgetEvent"] }], resourceEvent: [{ type: i0.Output, args: ["resourceEvent"] }] } });
36968
37107
 
36969
37108
  const PRAXIS_RELATED_RESOURCE_OUTLET_COMPONENT_METADATA = {
36970
37109
  id: 'praxis-related-resource-outlet',
@@ -36974,6 +37113,12 @@ const PRAXIS_RELATED_RESOURCE_OUTLET_COMPONENT_METADATA = {
36974
37113
  description: 'Runtime genérico para materializar surface.relatedResource como tabela filha governada por metadata.',
36975
37114
  icon: 'hub',
36976
37115
  inputs: [
37116
+ { name: 'surfaceId', type: 'string | null', description: 'Identidade declarativa da surface quando o outlet deve descobri-la no catálogo do recurso.' },
37117
+ { name: 'surfaceCatalog', type: 'ResourceSurfaceCatalogResponse | null', description: 'Catálogo de surfaces já carregado pelo host; evita nova consulta quando disponível.' },
37118
+ { name: 'discoverySource', type: 'ResourceLinkSource | null', description: 'Resposta HATEOAS ou mapa de links usado para seguir rel surfaces quando surface não foi fornecida.' },
37119
+ { name: 'parentLinks', type: 'RestApiLinks | RestApiResponse<unknown> | null', description: 'Atalho para links do registro pai; tem o mesmo papel de discoverySource para rel surfaces.' },
37120
+ { name: 'apiEndpointKey', type: 'ApiEndpoint | null', description: 'Endpoint Praxis usado pelo ResourceDiscoveryService ao resolver links ou fallback do catálogo.' },
37121
+ { name: 'apiUrlEntry', type: 'ApiUrlEntry | null', description: 'Entrada de API já resolvida para hosts destacados ou integrações remotas.' },
36977
37122
  { name: 'surface', type: 'ResourceSurfaceCatalogItem | null', description: 'Surface de catálogo que pode publicar relatedResource.' },
36978
37123
  { name: 'parentRecord', type: 'Record<string, unknown> | null', description: 'Registro pai usado para resolver parentIdPathVariable.' },
36979
37124
  { name: 'parentResourceId', type: 'string | number | null', description: 'Identificador explícito do registro pai quando não vem do record.' },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@praxisui/core",
3
- "version": "9.0.0-beta.37",
3
+ "version": "9.0.0-beta.38",
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",
@@ -14925,7 +14925,16 @@ type RelatedResourceOutletMode = 'inline' | 'open-action';
14925
14925
  declare class PraxisRelatedResourceOutletComponent {
14926
14926
  private readonly resolver;
14927
14927
  private readonly i18n;
14928
+ private readonly injector;
14929
+ private discoverySubscription?;
14930
+ private discoveryRequestKey;
14928
14931
  readonly surface: i0.InputSignal<ResourceSurfaceCatalogItem | null>;
14932
+ readonly surfaceId: i0.InputSignal<string | null>;
14933
+ readonly surfaceCatalog: i0.InputSignal<ResourceSurfaceCatalogResponse | null>;
14934
+ readonly discoverySource: i0.InputSignal<ResourceLinkSource | null>;
14935
+ readonly parentLinks: i0.InputSignal<RestApiLinks | RestApiResponse<unknown> | null>;
14936
+ readonly apiEndpointKey: i0.InputSignal<ApiEndpoint | null>;
14937
+ readonly apiUrlEntry: i0.InputSignal<ApiUrlEntry | null>;
14929
14938
  readonly parentRecord: i0.InputSignal<Record<string, unknown> | null>;
14930
14939
  readonly parentResourceId: i0.InputSignal<string | number | null>;
14931
14940
  readonly parentResourcePath: i0.InputSignal<string | null>;
@@ -14944,8 +14953,12 @@ declare class PraxisRelatedResourceOutletComponent {
14944
14953
  readonly surfaceOpen: i0.OutputEmitterRef<SurfaceOpenPayload>;
14945
14954
  readonly widgetEvent: i0.OutputEmitterRef<WidgetEventEnvelope>;
14946
14955
  readonly resourceEvent: i0.OutputEmitterRef<PraxisResourceEvent<unknown>>;
14956
+ private readonly discoveredSurface;
14957
+ private readonly discoveryState;
14958
+ private readonly discoveryStateReason;
14947
14959
  readonly resolution: i0.Signal<RelatedResourceSurfaceResolution>;
14948
14960
  readonly isBusy: i0.Signal<boolean>;
14961
+ constructor();
14949
14962
  openRelated(): void;
14950
14963
  onWidgetEvent(event: WidgetEventEnvelope): void;
14951
14964
  stateIcon(): string;
@@ -14954,9 +14967,17 @@ declare class PraxisRelatedResourceOutletComponent {
14954
14967
  t(key: string, fallback: string): string;
14955
14968
  private defaultTitle;
14956
14969
  private defaultDescription;
14970
+ private applyCatalogSurface;
14971
+ private resetDiscoveryState;
14972
+ private discoveryOptions;
14973
+ private resolveFallbackSurfaceCatalogHref;
14974
+ private normalizeResourcePath;
14975
+ private buildDiscoveryRequestKey;
14976
+ private isPermissionError;
14957
14977
  private extractResourceEvent;
14978
+ private trim;
14958
14979
  static ɵfac: i0.ɵɵFactoryDeclaration<PraxisRelatedResourceOutletComponent, never>;
14959
- static ɵcmp: i0.ɵɵComponentDeclaration<PraxisRelatedResourceOutletComponent, "praxis-related-resource-outlet", never, { "surface": { "alias": "surface"; "required": false; "isSignal": true; }; "parentRecord": { "alias": "parentRecord"; "required": false; "isSignal": true; }; "parentResourceId": { "alias": "parentResourceId"; "required": false; "isSignal": true; }; "parentResourcePath": { "alias": "parentResourcePath"; "required": false; "isSignal": true; }; "presentation": { "alias": "presentation"; "required": false; "isSignal": true; }; "title": { "alias": "title"; "required": false; "isSignal": true; }; "subtitle": { "alias": "subtitle"; "required": false; "isSignal": true; }; "icon": { "alias": "icon"; "required": false; "isSignal": true; }; "tableId": { "alias": "tableId"; "required": false; "isSignal": true; }; "queryContext": { "alias": "queryContext"; "required": false; "isSignal": true; }; "mode": { "alias": "mode"; "required": false; "isSignal": true; }; "state": { "alias": "state"; "required": false; "isSignal": true; }; "stateReason": { "alias": "stateReason"; "required": false; "isSignal": true; }; "compact": { "alias": "compact"; "required": false; "isSignal": true; }; "strictValidation": { "alias": "strictValidation"; "required": false; "isSignal": true; }; "ownerWidgetKey": { "alias": "ownerWidgetKey"; "required": false; "isSignal": true; }; }, { "surfaceOpen": "surfaceOpen"; "widgetEvent": "widgetEvent"; "resourceEvent": "resourceEvent"; }, never, never, true, never>;
14980
+ static ɵcmp: i0.ɵɵComponentDeclaration<PraxisRelatedResourceOutletComponent, "praxis-related-resource-outlet", never, { "surface": { "alias": "surface"; "required": false; "isSignal": true; }; "surfaceId": { "alias": "surfaceId"; "required": false; "isSignal": true; }; "surfaceCatalog": { "alias": "surfaceCatalog"; "required": false; "isSignal": true; }; "discoverySource": { "alias": "discoverySource"; "required": false; "isSignal": true; }; "parentLinks": { "alias": "parentLinks"; "required": false; "isSignal": true; }; "apiEndpointKey": { "alias": "apiEndpointKey"; "required": false; "isSignal": true; }; "apiUrlEntry": { "alias": "apiUrlEntry"; "required": false; "isSignal": true; }; "parentRecord": { "alias": "parentRecord"; "required": false; "isSignal": true; }; "parentResourceId": { "alias": "parentResourceId"; "required": false; "isSignal": true; }; "parentResourcePath": { "alias": "parentResourcePath"; "required": false; "isSignal": true; }; "presentation": { "alias": "presentation"; "required": false; "isSignal": true; }; "title": { "alias": "title"; "required": false; "isSignal": true; }; "subtitle": { "alias": "subtitle"; "required": false; "isSignal": true; }; "icon": { "alias": "icon"; "required": false; "isSignal": true; }; "tableId": { "alias": "tableId"; "required": false; "isSignal": true; }; "queryContext": { "alias": "queryContext"; "required": false; "isSignal": true; }; "mode": { "alias": "mode"; "required": false; "isSignal": true; }; "state": { "alias": "state"; "required": false; "isSignal": true; }; "stateReason": { "alias": "stateReason"; "required": false; "isSignal": true; }; "compact": { "alias": "compact"; "required": false; "isSignal": true; }; "strictValidation": { "alias": "strictValidation"; "required": false; "isSignal": true; }; "ownerWidgetKey": { "alias": "ownerWidgetKey"; "required": false; "isSignal": true; }; }, { "surfaceOpen": "surfaceOpen"; "widgetEvent": "widgetEvent"; "resourceEvent": "resourceEvent"; }, never, never, true, never>;
14960
14981
  }
14961
14982
 
14962
14983
  declare const PRAXIS_RELATED_RESOURCE_OUTLET_COMPONENT_METADATA: ComponentDocMeta;