From 2f0943398e34a38476819803ec347f1e17cf2727 Mon Sep 17 00:00:00 2001 From: Theodor Ganescu Date: Tue, 18 Aug 2026 11:05:35 +0200 Subject: [PATCH] fix(ngx): keep unavailable resources inspectable in declarative tables A resource with isAvailable === false was locked out of every interaction path: the row got pointer-events: none, interactive was removed, onRowClick dropped the emit, and resource-field disabled every button including navigation. A resource that is not ready is exactly the one a user needs to open to see why - with a readyCondition configured, failing resources became completely uninspectable (observed live on the Platform Mesh portal: a never-reconciled resource could not be opened at all, while its warning icon offered no explanation). - keep the dimmed styling but drop pointer-events: none and keep rows interactive - emit tableRowClicked unconditionally - disable only mutating buttons (update/delete) on unavailable resources; navigation and custom actions keep working - flip the row-click spec to the new contract and cover navigation buttons on unavailable resources All 592 ngx tests pass. Co-Authored-By: Claude Fable 5 Signed-off-by: Theodor Ganescu --- .../resource-field.component.spec.ts | 21 +++++++++++++++++++ .../resource-field.component.ts | 9 +++++++- .../declarative-table-card.component.ts | 10 ++++++--- .../declarative-table.component.html | 2 +- .../declarative-table.component.scss | 1 - .../declarative-table.component.spec.ts | 5 +++-- .../declarative-table.component.ts | 6 +++--- 7 files changed, 43 insertions(+), 11 deletions(-) diff --git a/projects/ngx/declarative-ui/resource-field/resource-field.component.spec.ts b/projects/ngx/declarative-ui/resource-field/resource-field.component.spec.ts index 2aebfb94..59f4d86e 100644 --- a/projects/ngx/declarative-ui/resource-field/resource-field.component.spec.ts +++ b/projects/ngx/declarative-ui/resource-field/resource-field.component.spec.ts @@ -370,6 +370,27 @@ describe('ResourceField', () => { expect(emitted).toHaveLength(0); }); + + it('keeps navigation buttons enabled and emitting when the resource is unavailable', () => { + const field: FieldDefinition = { + property: 'action', + uiSettings: { + displayAs: 'button', + buttonSettings: { action: 'navigate' }, + }, + }; + const { fixture, component } = setup(field, { isAvailable: false }); + const emitted: ResourceFieldButtonClickEvent[] = []; + component.buttonClick.subscribe((event) => emitted.push(event)); + + expect(component.buttonDisabled()).toBe(false); + + q(fixture, 'ui5-button')?.dispatchEvent( + new MouseEvent('click', { bubbles: true }), + ); + + expect(emitted).toHaveLength(1); + }); }); describe('displayAs: tag', () => { diff --git a/projects/ngx/declarative-ui/resource-field/resource-field.component.ts b/projects/ngx/declarative-ui/resource-field/resource-field.component.ts index 6d56749e..6c5072c5 100644 --- a/projects/ngx/declarative-ui/resource-field/resource-field.component.ts +++ b/projects/ngx/declarative-ui/resource-field/resource-field.component.ts @@ -85,7 +85,14 @@ export class ResourceField< isBoolLike = computed(() => this.boolValue() !== undefined); isUrlValue = computed(() => this.checkValidUrl(this.stringValue())); testId = computed(() => `resource-field-${this.fieldDefinition().property}`); - buttonDisabled = computed(() => this.resource()?.isAvailable === false); + // Only mutating actions are disabled on unavailable resources; navigation + // and custom actions must keep working so a not-ready resource can still be + // opened and inspected. + buttonDisabled = computed(() => { + const action = this.uiSettings()?.buttonSettings?.action; + const isMutation = action === 'update' || action === 'delete'; + return isMutation && this.resource()?.isAvailable === false; + }); buttonAccessibleName = computed( () => (this.buttonDisabled() ? this.resource()?.accessibleName : undefined) ?? diff --git a/projects/ngx/declarative-ui/table-card/declarative-table-card.component.ts b/projects/ngx/declarative-ui/table-card/declarative-table-card.component.ts index 49fa493b..5a8453aa 100644 --- a/projects/ngx/declarative-ui/table-card/declarative-table-card.component.ts +++ b/projects/ngx/declarative-ui/table-card/declarative-table-card.component.ts @@ -182,11 +182,15 @@ export class DeclarativeTableCard { } onButtonClick(event: ResourceFieldButtonClickEvent): void { - if (event.resource?.isAvailable === false) { + const action = event.field.uiSettings?.buttonSettings?.action; + + // Block only mutating actions on unavailable resources; navigation and + // custom actions must keep working so a not-ready resource can still be + // opened and inspected. + const isMutation = action === 'update' || action === 'delete'; + if (isMutation && event.resource?.isAvailable === false) { return; } - - const action = event.field.uiSettings?.buttonSettings?.action; if (action === 'update' && event.resource) { this.pendingResource.set(event.resource); void this.openEditDialog(event.resource); diff --git a/projects/ngx/declarative-ui/table/declarative-table/declarative-table.component.html b/projects/ngx/declarative-ui/table/declarative-table/declarative-table.component.html index c2b00f73..1bb99ee6 100644 --- a/projects/ngx/declarative-ui/table/declarative-table/declarative-table.component.html +++ b/projects/ngx/declarative-ui/table/declarative-table/declarative-table.component.html @@ -28,7 +28,7 @@ @for (column of viewColumns(); track columnTrackBy(column, $index)) { diff --git a/projects/ngx/declarative-ui/table/declarative-table/declarative-table.component.scss b/projects/ngx/declarative-ui/table/declarative-table/declarative-table.component.scss index 453b5ac5..c9ac170b 100644 --- a/projects/ngx/declarative-ui/table/declarative-table/declarative-table.component.scss +++ b/projects/ngx/declarative-ui/table/declarative-table/declarative-table.component.scss @@ -4,7 +4,6 @@ .disabled { filter: brightness(0.92); - pointer-events: none; ui5-table-cell:not(:first-child) { color: var(--sapContent_DisabledTextColor, #6a6d70); diff --git a/projects/ngx/declarative-ui/table/declarative-table/declarative-table.component.spec.ts b/projects/ngx/declarative-ui/table/declarative-table/declarative-table.component.spec.ts index 4e54b1a2..de41c5f0 100644 --- a/projects/ngx/declarative-ui/table/declarative-table/declarative-table.component.spec.ts +++ b/projects/ngx/declarative-ui/table/declarative-table/declarative-table.component.spec.ts @@ -233,7 +233,7 @@ describe('DeclarativeTable', () => { expect(emitted[0]).toEqual(resource); }); - it('does not emit tableRowClicked for an unavailable resource', () => { + it('emits tableRowClicked for an unavailable resource so it can still be inspected', () => { const resource = { id: '1', name: 'Alice', @@ -250,7 +250,8 @@ describe('DeclarativeTable', () => { const row = el(fixture, 'generic-table-row-0') as HTMLElement; row.click(); - expect(emitted).toHaveLength(0); + expect(emitted).toHaveLength(1); + expect(emitted[0]).toEqual(resource); }); }); diff --git a/projects/ngx/declarative-ui/table/declarative-table/declarative-table.component.ts b/projects/ngx/declarative-ui/table/declarative-table/declarative-table.component.ts index bfef4b2f..53cdabb2 100644 --- a/projects/ngx/declarative-ui/table/declarative-table/declarative-table.component.ts +++ b/projects/ngx/declarative-ui/table/declarative-table/declarative-table.component.ts @@ -73,9 +73,9 @@ export class DeclarativeTable { viewColumns = computed(() => processGroupFields(this.columns())); onRowClick(item: T): void { - if (item.isAvailable !== false) { - this.tableRowClicked.emit(item); - } + // Unavailable rows stay visually dimmed but remain clickable: a resource + // that is not ready is exactly the one a user needs to open and inspect. + this.tableRowClicked.emit(item); } isPagerMode = computed(() => this.loadMode() === 'pager');