@openmfp/ngx 1.3.0 → 1.4.1

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.
@@ -2295,6 +2295,48 @@ const decodeBase64 = (base64) => {
2295
2295
  throw new Error('Failed to decode Base64 string');
2296
2296
  }
2297
2297
  };
2298
+ /**
2299
+ * Replaces every `{{path}}` placeholder in `template` with the value resolved
2300
+ * from `resource` via the given path (dot-notation). Unresolved placeholders
2301
+ * become an empty string.
2302
+ *
2303
+ * After substitution, the resulting href is resolved against `baseHref`
2304
+ * (typically the current `window.location.href`):
2305
+ * - An absolute URL (with a protocol, e.g. `https://…`) is returned unchanged.
2306
+ * - A relative path is resolved via the standard URL algorithm:
2307
+ * a leading `/` resolves from the origin root, otherwise it resolves
2308
+ * relative to the current path.
2309
+ */
2310
+ const resolveLinkTemplate = (template, resource, baseHref = window.location.href) => {
2311
+ const substituted = template.replace(/\{\{\s*([^}]+?)\s*\}\}/g, (_match, path) => {
2312
+ if (!resource)
2313
+ return '';
2314
+ const value = getResourceValueByJsonPath(resource, { property: path });
2315
+ return value == null ? '' : String(value);
2316
+ });
2317
+ if (!substituted || isAbsoluteUrl(substituted)) {
2318
+ return substituted;
2319
+ }
2320
+ try {
2321
+ return new URL(substituted, baseHref + '/').href;
2322
+ }
2323
+ catch {
2324
+ return substituted;
2325
+ }
2326
+ };
2327
+ /**
2328
+ * True when `value` parses as an absolute URL on its own (has a protocol,
2329
+ * e.g. `https://…`, `mailto:…`). Relative paths return `false`.
2330
+ */
2331
+ const isAbsoluteUrl = (value) => {
2332
+ try {
2333
+ new URL(value);
2334
+ return true;
2335
+ }
2336
+ catch {
2337
+ return false;
2338
+ }
2339
+ };
2298
2340
 
2299
2341
  function getFieldValue(field, resource) {
2300
2342
  if (resource) {
@@ -2396,16 +2438,18 @@ class LinkValue {
2396
2438
  ...(ngDevMode ? [{ debugName: "urlValue" }] : /* istanbul ignore next */ []));
2397
2439
  testId = input('link-value-link', /* @ts-ignore */
2398
2440
  ...(ngDevMode ? [{ debugName: "testId" }] : /* istanbul ignore next */ []));
2441
+ displayValue = input(/* @ts-ignore */
2442
+ ...(ngDevMode ? [undefined, { debugName: "displayValue" }] : /* istanbul ignore next */ []));
2399
2443
  stopPropagation(event) {
2400
2444
  event.stopPropagation();
2401
2445
  }
2402
2446
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: LinkValue, deps: [], target: i0.ɵɵFactoryTarget.Component });
2403
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.1.2", type: LinkValue, isStandalone: true, selector: "mfp-link-value", inputs: { urlValue: { classPropertyName: "urlValue", publicName: "urlValue", isSignal: true, isRequired: true, transformFunction: null }, testId: { classPropertyName: "testId", publicName: "testId", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<ui5-link\n target=\"_blank\"\n [attr.data-testid]=\"testId()\"\n [href]=\"urlValue()\"\n [tooltip]=\"urlValue()\"\n (click)=\"stopPropagation($event)\"\n >Link</ui5-link\n>\n", styles: [""], dependencies: [{ kind: "component", type: Link, selector: "ui5-link, [ui5-link]", inputs: ["accessibilityAttributes", "accessibleDescription", "accessibleName", "accessibleNameRef", "accessibleRole", "design", "disabled", "endIcon", "href", "icon", "interactiveAreaSize", "target", "tooltip", "wrappingType"], outputs: ["ui5Click"], exportAs: ["ui5Link"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2447
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.1.2", type: LinkValue, isStandalone: true, selector: "mfp-link-value", inputs: { urlValue: { classPropertyName: "urlValue", publicName: "urlValue", isSignal: true, isRequired: true, transformFunction: null }, testId: { classPropertyName: "testId", publicName: "testId", isSignal: true, isRequired: false, transformFunction: null }, displayValue: { classPropertyName: "displayValue", publicName: "displayValue", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<ui5-link\n target=\"_blank\"\n [attr.data-testid]=\"testId()\"\n [href]=\"urlValue()\"\n [tooltip]=\"urlValue()\"\n (click)=\"stopPropagation($event)\"\n >{{ displayValue() || urlValue() }}</ui5-link\n>\n", styles: [""], dependencies: [{ kind: "component", type: Link, selector: "ui5-link, [ui5-link]", inputs: ["accessibilityAttributes", "accessibleDescription", "accessibleName", "accessibleNameRef", "accessibleRole", "design", "disabled", "endIcon", "href", "icon", "interactiveAreaSize", "target", "tooltip", "wrappingType"], outputs: ["ui5Click"], exportAs: ["ui5Link"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2404
2448
  }
2405
2449
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: LinkValue, decorators: [{
2406
2450
  type: Component,
2407
- args: [{ selector: 'mfp-link-value', imports: [Link], changeDetection: ChangeDetectionStrategy.OnPush, schemas: [CUSTOM_ELEMENTS_SCHEMA], template: "<ui5-link\n target=\"_blank\"\n [attr.data-testid]=\"testId()\"\n [href]=\"urlValue()\"\n [tooltip]=\"urlValue()\"\n (click)=\"stopPropagation($event)\"\n >Link</ui5-link\n>\n" }]
2408
- }], propDecorators: { urlValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "urlValue", required: true }] }], testId: [{ type: i0.Input, args: [{ isSignal: true, alias: "testId", required: false }] }] } });
2451
+ args: [{ selector: 'mfp-link-value', imports: [Link], changeDetection: ChangeDetectionStrategy.OnPush, schemas: [CUSTOM_ELEMENTS_SCHEMA], template: "<ui5-link\n target=\"_blank\"\n [attr.data-testid]=\"testId()\"\n [href]=\"urlValue()\"\n [tooltip]=\"urlValue()\"\n (click)=\"stopPropagation($event)\"\n >{{ displayValue() || urlValue() }}</ui5-link\n>\n" }]
2452
+ }], propDecorators: { urlValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "urlValue", required: true }] }], testId: [{ type: i0.Input, args: [{ isSignal: true, alias: "testId", required: false }] }], displayValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "displayValue", required: false }] }] } });
2409
2453
 
2410
2454
  /**
2411
2455
  * Read-only display for a `propertyCollection` field. Renders the resolved
@@ -2548,8 +2592,17 @@ class ResourceField {
2548
2592
  ...(ngDevMode ? [{ debugName: "displayValue" }] : /* istanbul ignore next */ []));
2549
2593
  isBoolLike = computed(() => this.boolValue() !== undefined, /* @ts-ignore */
2550
2594
  ...(ngDevMode ? [{ debugName: "isBoolLike" }] : /* istanbul ignore next */ []));
2551
- isUrlValue = computed(() => this.checkValidUrl(this.stringValue()), /* @ts-ignore */
2552
- ...(ngDevMode ? [{ debugName: "isUrlValue" }] : /* istanbul ignore next */ []));
2595
+ linkSettings = computed(() => this.uiSettings()?.linkSettings, /* @ts-ignore */
2596
+ ...(ngDevMode ? [{ debugName: "linkSettings" }] : /* istanbul ignore next */ []));
2597
+ linkHref = computed(() => {
2598
+ const template = this.linkSettings()?.link;
2599
+ return template
2600
+ ? resolveLinkTemplate(template, this.resource())
2601
+ : this.stringValue();
2602
+ }, /* @ts-ignore */
2603
+ ...(ngDevMode ? [{ debugName: "linkHref" }] : /* istanbul ignore next */ []));
2604
+ linkText = computed(() => this.linkSettings()?.text ?? this.stringValue(), /* @ts-ignore */
2605
+ ...(ngDevMode ? [{ debugName: "linkText" }] : /* istanbul ignore next */ []));
2553
2606
  testId = computed(() => `resource-field-${this.fieldDefinition().property}`, /* @ts-ignore */
2554
2607
  ...(ngDevMode ? [{ debugName: "testId" }] : /* istanbul ignore next */ []));
2555
2608
  buttonDisabled = computed(() => this.resource()?.isAvailable === false, /* @ts-ignore */
@@ -2617,18 +2670,6 @@ class ResourceField {
2617
2670
  }
2618
2671
  return [];
2619
2672
  }
2620
- checkValidUrl(value) {
2621
- if (!value) {
2622
- return false;
2623
- }
2624
- try {
2625
- new URL(value);
2626
- return true;
2627
- }
2628
- catch {
2629
- return false;
2630
- }
2631
- }
2632
2673
  copyValue(event) {
2633
2674
  event.stopPropagation();
2634
2675
  navigator.clipboard.writeText(this.value() || '').then(() => {
@@ -2650,7 +2691,7 @@ class ResourceField {
2650
2691
  });
2651
2692
  }
2652
2693
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: ResourceField, deps: [], target: i0.ɵɵFactoryTarget.Component });
2653
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.2", type: ResourceField, isStandalone: true, selector: "mfp-resource-field", inputs: { fieldDefinition: { classPropertyName: "fieldDefinition", publicName: "fieldDefinition", isSignal: true, isRequired: true, transformFunction: null }, resource: { classPropertyName: "resource", publicName: "resource", isSignal: true, isRequired: false, transformFunction: null }, permissions: { classPropertyName: "permissions", publicName: "permissions", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { buttonClick: "buttonClick" }, host: { properties: { "class.resource-field--collection": "isCollection()" } }, ngImport: i0, template: "@if (canRenderField()) {\n @if (isCollection()) {\n <mfp-resource-collection-field\n [fieldDefinition]=\"fieldDefinition()\"\n [resource]=\"resource()\"\n />\n } @else {\n @let displayType = displayAs();\n <span [attr.data-testid]=\"testId()\" [style]=\"cssStyles()\">\n @if (displayType === 'secret') {\n <span class=\"secret-value-container\">\n <mfp-secret-value\n [isVisible]=\"isVisible()\"\n [testId]=\"testId() + '-secret'\"\n [value]=\"value()\"\n />\n <ui5-icon\n class=\"toggle-icon\"\n mode=\"Interactive\"\n [accessibleName]=\"isVisible() ? 'Hide value' : 'Show value'\"\n [attr.data-testid]=\"testId() + '-secret-toggle'\"\n [name]=\"isVisible() ? 'hide' : 'show'\"\n (click)=\"toggleVisibility($event)\"\n />\n </span>\n } @else if (displayType === 'boolIcon' && isBoolLike()) {\n <mfp-boolean-value\n [boolValue]=\"boolValue()!\"\n [testId]=\"testId() + '-boolean'\"\n />\n } @else if (displayType === 'link' && isUrlValue()) {\n <mfp-link-value\n [testId]=\"testId() + '-link'\"\n [urlValue]=\"stringValue()!\"\n />\n } @else if (displayType === 'tooltip') {\n <ui5-icon\n [accessibleName]=\"stringValue()!\"\n [attr.data-testid]=\"testId() + '-tooltip'\"\n [name]=\"tooltipIcon() ?? 'hint'\"\n [showTooltip]=\"true\"\n />\n } @else if (displayType === 'alert') {\n @if (!value()) {\n <ui5-icon\n design=\"Critical\"\n name=\"alert\"\n [accessibleName]=\"resource()?.accessibleName ?? ''\"\n [attr.data-testid]=\"testId() + '-icon'\"\n [showTooltip]=\"true\"\n (click)=\"$event.stopPropagation()\"\n />\n }\n } @else if (displayType === 'img' && value()) {\n <img alt=\"Resource image\" class=\"image-cell\" [src]=\"value()\" />\n } @else if (displayType === 'button') {\n @let buttonSettings = uiSettings()?.buttonSettings;\n <ui5-button\n [accessibleName]=\"buttonAccessibleName()\"\n [attr.data-testid]=\"testId() + '-button'\"\n [design]=\"buttonSettings?.design\"\n [disabled]=\"buttonDisabled()\"\n [endIcon]=\"buttonSettings?.endIcon\"\n [icon]=\"buttonSettings?.icon\"\n [tooltip]=\"buttonSettings?.tooltip\"\n (click)=\"buttonClicked($event)\"\n >{{ buttonSettings?.text }}</ui5-button\n >&nbsp;\n } @else if (displayType === 'tag') {\n <mfp-tag-list-value\n [tagSettings]=\"uiSettings()?.tagSettings\"\n [tags]=\"tags()\"\n [testId]=\"testId() + '-tags'\"\n />\n } @else {\n {{ displayValue() }}\n }\n </span>\n\n @if (withCopyButton()) {\n <ui5-icon\n class=\"toggle-icon\"\n mode=\"Interactive\"\n [accessibleName]=\"copySuccess() ? 'Copied' : 'Copy value'\"\n [attr.data-testid]=\"testId() + '-copy'\"\n [design]=\"copySuccess() ? 'Positive' : 'Default'\"\n [name]=\"copySuccess() ? 'accept' : 'copy'\"\n (click)=\"copyValue($event)\"\n />\n }\n }\n}\n", styles: [":host{display:inline-flex;align-items:center}:host(.resource-field--collection){display:block;width:100%}.secret-value-container{display:flex;align-items:center;gap:.25rem}.toggle-icon{cursor:pointer;transition:color .2s ease;padding:.25rem}.toggle-icon:hover{color:var(--sapButton_Hover_TextColor, #0854a0)}\n"], dependencies: [{ kind: "component", type: i0.forwardRef(() => Icon), selector: "ui5-icon, [ui5-icon]", inputs: ["accessibleName", "design", "mode", "name", "showTooltip"], outputs: ["ui5Click"], exportAs: ["ui5Icon"] }, { kind: "component", type: i0.forwardRef(() => BooleanValue), selector: "mfp-boolean-value", inputs: ["boolValue", "testId"] }, { kind: "component", type: i0.forwardRef(() => LinkValue), selector: "mfp-link-value", inputs: ["urlValue", "testId"] }, { kind: "component", type: i0.forwardRef(() => SecretValue), selector: "mfp-secret-value", inputs: ["value", "isVisible", "testId"] }, { kind: "component", type: i0.forwardRef(() => Button), selector: "ui5-button, [ui5-button]", inputs: ["accessibilityAttributes", "accessibleDescription", "accessibleName", "accessibleNameRef", "accessibleRole", "design", "disabled", "endIcon", "form", "icon", "loading", "loadingDelay", "submits", "tooltip", "type"], outputs: ["ui5Click"], exportAs: ["ui5Button"] }, { kind: "component", type: i0.forwardRef(() => TagListValue), selector: "mfp-tag-list-value", inputs: ["tags", "tagSettings", "testId"] }, { kind: "component", type: i0.forwardRef(() => ResourceCollectionField), selector: "mfp-resource-collection-field", inputs: ["fieldDefinition", "resource"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.ShadowDom });
2694
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.2", type: ResourceField, isStandalone: true, selector: "mfp-resource-field", inputs: { fieldDefinition: { classPropertyName: "fieldDefinition", publicName: "fieldDefinition", isSignal: true, isRequired: true, transformFunction: null }, resource: { classPropertyName: "resource", publicName: "resource", isSignal: true, isRequired: false, transformFunction: null }, permissions: { classPropertyName: "permissions", publicName: "permissions", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { buttonClick: "buttonClick" }, host: { properties: { "class.resource-field--collection": "isCollection()" } }, ngImport: i0, template: "@if (canRenderField()) {\n @if (isCollection()) {\n <mfp-resource-collection-field\n [fieldDefinition]=\"fieldDefinition()\"\n [resource]=\"resource()\"\n />\n } @else {\n @let displayType = displayAs();\n <span [attr.data-testid]=\"testId()\" [style]=\"cssStyles()\">\n @if (displayType === 'secret') {\n <span class=\"secret-value-container\">\n <mfp-secret-value\n [isVisible]=\"isVisible()\"\n [testId]=\"testId() + '-secret'\"\n [value]=\"value()\"\n />\n <ui5-icon\n class=\"toggle-icon\"\n mode=\"Interactive\"\n [accessibleName]=\"isVisible() ? 'Hide value' : 'Show value'\"\n [attr.data-testid]=\"testId() + '-secret-toggle'\"\n [name]=\"isVisible() ? 'hide' : 'show'\"\n (click)=\"toggleVisibility($event)\"\n />\n </span>\n } @else if (displayType === 'boolIcon' && isBoolLike()) {\n <mfp-boolean-value\n [boolValue]=\"boolValue()!\"\n [testId]=\"testId() + '-boolean'\"\n />\n } @else if (displayType === 'link' && linkHref()) {\n <mfp-link-value\n [displayValue]=\"linkText()\"\n [testId]=\"testId() + '-link'\"\n [urlValue]=\"linkHref()!\"\n />\n } @else if (displayType === 'tooltip') {\n <ui5-icon\n [accessibleName]=\"stringValue()!\"\n [attr.data-testid]=\"testId() + '-tooltip'\"\n [name]=\"tooltipIcon() ?? 'hint'\"\n [showTooltip]=\"true\"\n />\n } @else if (displayType === 'alert') {\n @if (!value()) {\n <ui5-icon\n design=\"Critical\"\n name=\"alert\"\n [accessibleName]=\"resource()?.accessibleName ?? ''\"\n [attr.data-testid]=\"testId() + '-icon'\"\n [showTooltip]=\"true\"\n (click)=\"$event.stopPropagation()\"\n />\n }\n } @else if (displayType === 'img' && value()) {\n <img alt=\"Resource image\" class=\"image-cell\" [src]=\"value()\" />\n } @else if (displayType === 'button') {\n @let buttonSettings = uiSettings()?.buttonSettings;\n <ui5-button\n [accessibleName]=\"buttonAccessibleName()\"\n [attr.data-testid]=\"testId() + '-button'\"\n [design]=\"buttonSettings?.design\"\n [disabled]=\"buttonDisabled()\"\n [endIcon]=\"buttonSettings?.endIcon\"\n [icon]=\"buttonSettings?.icon\"\n [tooltip]=\"buttonSettings?.tooltip\"\n (click)=\"buttonClicked($event)\"\n >{{ buttonSettings?.text }}</ui5-button\n >&nbsp;\n } @else if (displayType === 'tag') {\n <mfp-tag-list-value\n [tagSettings]=\"uiSettings()?.tagSettings\"\n [tags]=\"tags()\"\n [testId]=\"testId() + '-tags'\"\n />\n } @else {\n {{ displayValue() }}\n }\n </span>\n\n @if (withCopyButton()) {\n <ui5-icon\n class=\"toggle-icon\"\n mode=\"Interactive\"\n [accessibleName]=\"copySuccess() ? 'Copied' : 'Copy value'\"\n [attr.data-testid]=\"testId() + '-copy'\"\n [design]=\"copySuccess() ? 'Positive' : 'Default'\"\n [name]=\"copySuccess() ? 'accept' : 'copy'\"\n (click)=\"copyValue($event)\"\n />\n }\n }\n}\n", styles: [":host{display:inline-flex;align-items:center}:host(.resource-field--collection){display:block;width:100%}.secret-value-container{display:flex;align-items:center;gap:.25rem}.toggle-icon{cursor:pointer;transition:color .2s ease;padding:.25rem}.toggle-icon:hover{color:var(--sapButton_Hover_TextColor, #0854a0)}\n"], dependencies: [{ kind: "component", type: i0.forwardRef(() => Icon), selector: "ui5-icon, [ui5-icon]", inputs: ["accessibleName", "design", "mode", "name", "showTooltip"], outputs: ["ui5Click"], exportAs: ["ui5Icon"] }, { kind: "component", type: i0.forwardRef(() => BooleanValue), selector: "mfp-boolean-value", inputs: ["boolValue", "testId"] }, { kind: "component", type: i0.forwardRef(() => LinkValue), selector: "mfp-link-value", inputs: ["urlValue", "testId", "displayValue"] }, { kind: "component", type: i0.forwardRef(() => SecretValue), selector: "mfp-secret-value", inputs: ["value", "isVisible", "testId"] }, { kind: "component", type: i0.forwardRef(() => Button), selector: "ui5-button, [ui5-button]", inputs: ["accessibilityAttributes", "accessibleDescription", "accessibleName", "accessibleNameRef", "accessibleRole", "design", "disabled", "endIcon", "form", "icon", "loading", "loadingDelay", "submits", "tooltip", "type"], outputs: ["ui5Click"], exportAs: ["ui5Button"] }, { kind: "component", type: i0.forwardRef(() => TagListValue), selector: "mfp-tag-list-value", inputs: ["tags", "tagSettings", "testId"] }, { kind: "component", type: i0.forwardRef(() => ResourceCollectionField), selector: "mfp-resource-collection-field", inputs: ["fieldDefinition", "resource"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.ShadowDom });
2654
2695
  }
2655
2696
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: ResourceField, decorators: [{
2656
2697
  type: Component,
@@ -2668,7 +2709,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImpor
2668
2709
  forwardRef(() => ResourceCollectionField),
2669
2710
  ], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.ShadowDom, host: {
2670
2711
  '[class.resource-field--collection]': 'isCollection()',
2671
- }, schemas: [CUSTOM_ELEMENTS_SCHEMA], template: "@if (canRenderField()) {\n @if (isCollection()) {\n <mfp-resource-collection-field\n [fieldDefinition]=\"fieldDefinition()\"\n [resource]=\"resource()\"\n />\n } @else {\n @let displayType = displayAs();\n <span [attr.data-testid]=\"testId()\" [style]=\"cssStyles()\">\n @if (displayType === 'secret') {\n <span class=\"secret-value-container\">\n <mfp-secret-value\n [isVisible]=\"isVisible()\"\n [testId]=\"testId() + '-secret'\"\n [value]=\"value()\"\n />\n <ui5-icon\n class=\"toggle-icon\"\n mode=\"Interactive\"\n [accessibleName]=\"isVisible() ? 'Hide value' : 'Show value'\"\n [attr.data-testid]=\"testId() + '-secret-toggle'\"\n [name]=\"isVisible() ? 'hide' : 'show'\"\n (click)=\"toggleVisibility($event)\"\n />\n </span>\n } @else if (displayType === 'boolIcon' && isBoolLike()) {\n <mfp-boolean-value\n [boolValue]=\"boolValue()!\"\n [testId]=\"testId() + '-boolean'\"\n />\n } @else if (displayType === 'link' && isUrlValue()) {\n <mfp-link-value\n [testId]=\"testId() + '-link'\"\n [urlValue]=\"stringValue()!\"\n />\n } @else if (displayType === 'tooltip') {\n <ui5-icon\n [accessibleName]=\"stringValue()!\"\n [attr.data-testid]=\"testId() + '-tooltip'\"\n [name]=\"tooltipIcon() ?? 'hint'\"\n [showTooltip]=\"true\"\n />\n } @else if (displayType === 'alert') {\n @if (!value()) {\n <ui5-icon\n design=\"Critical\"\n name=\"alert\"\n [accessibleName]=\"resource()?.accessibleName ?? ''\"\n [attr.data-testid]=\"testId() + '-icon'\"\n [showTooltip]=\"true\"\n (click)=\"$event.stopPropagation()\"\n />\n }\n } @else if (displayType === 'img' && value()) {\n <img alt=\"Resource image\" class=\"image-cell\" [src]=\"value()\" />\n } @else if (displayType === 'button') {\n @let buttonSettings = uiSettings()?.buttonSettings;\n <ui5-button\n [accessibleName]=\"buttonAccessibleName()\"\n [attr.data-testid]=\"testId() + '-button'\"\n [design]=\"buttonSettings?.design\"\n [disabled]=\"buttonDisabled()\"\n [endIcon]=\"buttonSettings?.endIcon\"\n [icon]=\"buttonSettings?.icon\"\n [tooltip]=\"buttonSettings?.tooltip\"\n (click)=\"buttonClicked($event)\"\n >{{ buttonSettings?.text }}</ui5-button\n >&nbsp;\n } @else if (displayType === 'tag') {\n <mfp-tag-list-value\n [tagSettings]=\"uiSettings()?.tagSettings\"\n [tags]=\"tags()\"\n [testId]=\"testId() + '-tags'\"\n />\n } @else {\n {{ displayValue() }}\n }\n </span>\n\n @if (withCopyButton()) {\n <ui5-icon\n class=\"toggle-icon\"\n mode=\"Interactive\"\n [accessibleName]=\"copySuccess() ? 'Copied' : 'Copy value'\"\n [attr.data-testid]=\"testId() + '-copy'\"\n [design]=\"copySuccess() ? 'Positive' : 'Default'\"\n [name]=\"copySuccess() ? 'accept' : 'copy'\"\n (click)=\"copyValue($event)\"\n />\n }\n }\n}\n", styles: [":host{display:inline-flex;align-items:center}:host(.resource-field--collection){display:block;width:100%}.secret-value-container{display:flex;align-items:center;gap:.25rem}.toggle-icon{cursor:pointer;transition:color .2s ease;padding:.25rem}.toggle-icon:hover{color:var(--sapButton_Hover_TextColor, #0854a0)}\n"] }]
2712
+ }, schemas: [CUSTOM_ELEMENTS_SCHEMA], template: "@if (canRenderField()) {\n @if (isCollection()) {\n <mfp-resource-collection-field\n [fieldDefinition]=\"fieldDefinition()\"\n [resource]=\"resource()\"\n />\n } @else {\n @let displayType = displayAs();\n <span [attr.data-testid]=\"testId()\" [style]=\"cssStyles()\">\n @if (displayType === 'secret') {\n <span class=\"secret-value-container\">\n <mfp-secret-value\n [isVisible]=\"isVisible()\"\n [testId]=\"testId() + '-secret'\"\n [value]=\"value()\"\n />\n <ui5-icon\n class=\"toggle-icon\"\n mode=\"Interactive\"\n [accessibleName]=\"isVisible() ? 'Hide value' : 'Show value'\"\n [attr.data-testid]=\"testId() + '-secret-toggle'\"\n [name]=\"isVisible() ? 'hide' : 'show'\"\n (click)=\"toggleVisibility($event)\"\n />\n </span>\n } @else if (displayType === 'boolIcon' && isBoolLike()) {\n <mfp-boolean-value\n [boolValue]=\"boolValue()!\"\n [testId]=\"testId() + '-boolean'\"\n />\n } @else if (displayType === 'link' && linkHref()) {\n <mfp-link-value\n [displayValue]=\"linkText()\"\n [testId]=\"testId() + '-link'\"\n [urlValue]=\"linkHref()!\"\n />\n } @else if (displayType === 'tooltip') {\n <ui5-icon\n [accessibleName]=\"stringValue()!\"\n [attr.data-testid]=\"testId() + '-tooltip'\"\n [name]=\"tooltipIcon() ?? 'hint'\"\n [showTooltip]=\"true\"\n />\n } @else if (displayType === 'alert') {\n @if (!value()) {\n <ui5-icon\n design=\"Critical\"\n name=\"alert\"\n [accessibleName]=\"resource()?.accessibleName ?? ''\"\n [attr.data-testid]=\"testId() + '-icon'\"\n [showTooltip]=\"true\"\n (click)=\"$event.stopPropagation()\"\n />\n }\n } @else if (displayType === 'img' && value()) {\n <img alt=\"Resource image\" class=\"image-cell\" [src]=\"value()\" />\n } @else if (displayType === 'button') {\n @let buttonSettings = uiSettings()?.buttonSettings;\n <ui5-button\n [accessibleName]=\"buttonAccessibleName()\"\n [attr.data-testid]=\"testId() + '-button'\"\n [design]=\"buttonSettings?.design\"\n [disabled]=\"buttonDisabled()\"\n [endIcon]=\"buttonSettings?.endIcon\"\n [icon]=\"buttonSettings?.icon\"\n [tooltip]=\"buttonSettings?.tooltip\"\n (click)=\"buttonClicked($event)\"\n >{{ buttonSettings?.text }}</ui5-button\n >&nbsp;\n } @else if (displayType === 'tag') {\n <mfp-tag-list-value\n [tagSettings]=\"uiSettings()?.tagSettings\"\n [tags]=\"tags()\"\n [testId]=\"testId() + '-tags'\"\n />\n } @else {\n {{ displayValue() }}\n }\n </span>\n\n @if (withCopyButton()) {\n <ui5-icon\n class=\"toggle-icon\"\n mode=\"Interactive\"\n [accessibleName]=\"copySuccess() ? 'Copied' : 'Copy value'\"\n [attr.data-testid]=\"testId() + '-copy'\"\n [design]=\"copySuccess() ? 'Positive' : 'Default'\"\n [name]=\"copySuccess() ? 'accept' : 'copy'\"\n (click)=\"copyValue($event)\"\n />\n }\n }\n}\n", styles: [":host{display:inline-flex;align-items:center}:host(.resource-field--collection){display:block;width:100%}.secret-value-container{display:flex;align-items:center;gap:.25rem}.toggle-icon{cursor:pointer;transition:color .2s ease;padding:.25rem}.toggle-icon:hover{color:var(--sapButton_Hover_TextColor, #0854a0)}\n"] }]
2672
2713
  }], propDecorators: { fieldDefinition: [{ type: i0.Input, args: [{ isSignal: true, alias: "fieldDefinition", required: true }] }], resource: [{ type: i0.Input, args: [{ isSignal: true, alias: "resource", required: false }] }], permissions: [{ type: i0.Input, args: [{ isSignal: true, alias: "permissions", required: false }] }], buttonClick: [{ type: i0.Output, args: ["buttonClick"] }] } });
2673
2714
 
2674
2715
  const processGroupFields = (fields) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openmfp/ngx",
3
- "version": "1.3.0",
3
+ "version": "1.4.1",
4
4
  "license": "Apache-2.0",
5
5
  "repository": {
6
6
  "type": "git",
@@ -26,6 +26,20 @@ interface PropertyField {
26
26
  /** Ordered list of transforms applied to the resolved string. */
27
27
  transform?: TransformType[];
28
28
  }
29
+ /** Configuration for a `displayAs: 'link'` cell. */
30
+ interface LinkSettings {
31
+ /**
32
+ * href template. Supports `{{path}}` placeholders resolved against the
33
+ * resource (e.g. `/{{metadata.name}}/accounts`). Absolute or relative.
34
+ * When omitted, the field value is used as the href.
35
+ */
36
+ link?: string;
37
+ /**
38
+ * Static visible text for the link. When set, overrides the field value as
39
+ * the link label. When omitted, the field value is used as the link text.
40
+ */
41
+ text?: string;
42
+ }
29
43
  /** Appearance settings for tag chip rendering. */
30
44
  interface TagSettings {
31
45
  design?: 'Neutral' | 'Positive' | 'Critical' | 'Negative' | 'Information' | 'Set1' | 'Set2';
@@ -41,6 +55,8 @@ interface UiSettings {
41
55
  buttonSettings?: ButtonSettings;
42
56
  /** Tag chip configuration — only used when `displayAs` is `'tag'`. */
43
57
  tagSettings?: TagSettings;
58
+ /** Link configuration — only used when `displayAs` is `'link'`. */
59
+ linkSettings?: LinkSettings;
44
60
  /** SAP UI5 icon name shown as the tooltip trigger icon. */
45
61
  tooltipIcon?: string;
46
62
  /** When `true`, a copy-to-clipboard button is rendered next to the value. */
@@ -1495,7 +1511,9 @@ declare class ResourceField<T extends GenericResource, F extends FieldDefinition
1495
1511
  }>;
1496
1512
  displayValue: _angular_core.Signal<string>;
1497
1513
  isBoolLike: _angular_core.Signal<boolean>;
1498
- isUrlValue: _angular_core.Signal<boolean>;
1514
+ linkSettings: _angular_core.Signal<_openmfp_ngx.LinkSettings | undefined>;
1515
+ linkHref: _angular_core.Signal<string | undefined>;
1516
+ linkText: _angular_core.Signal<string | undefined>;
1499
1517
  testId: _angular_core.Signal<string>;
1500
1518
  buttonDisabled: _angular_core.Signal<boolean>;
1501
1519
  buttonAccessibleName: _angular_core.Signal<string>;
@@ -1510,7 +1528,6 @@ declare class ResourceField<T extends GenericResource, F extends FieldDefinition
1510
1528
  private normalizeBoolean;
1511
1529
  private normalizeString;
1512
1530
  private normalizeTagsArray;
1513
- private checkValidUrl;
1514
1531
  copyValue(event: Event): void;
1515
1532
  protected buttonClicked(event: MouseEvent): void;
1516
1533
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<ResourceField<any, any>, never>;
@@ -1533,9 +1550,10 @@ declare class BooleanValue {
1533
1550
  declare class LinkValue {
1534
1551
  urlValue: _angular_core.InputSignal<string>;
1535
1552
  testId: _angular_core.InputSignal<string>;
1553
+ displayValue: _angular_core.InputSignal<string | undefined>;
1536
1554
  stopPropagation(event: Event): void;
1537
1555
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<LinkValue, never>;
1538
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<LinkValue, "mfp-link-value", never, { "urlValue": { "alias": "urlValue"; "required": true; "isSignal": true; }; "testId": { "alias": "testId"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
1556
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<LinkValue, "mfp-link-value", never, { "urlValue": { "alias": "urlValue"; "required": true; "isSignal": true; }; "testId": { "alias": "testId"; "required": false; "isSignal": true; }; "displayValue": { "alias": "displayValue"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
1539
1557
  }
1540
1558
 
1541
1559
  declare class SecretValue {
@@ -1615,4 +1633,4 @@ declare class MockCard {
1615
1633
  }
1616
1634
 
1617
1635
  export { BooleanValue, CARD_TYPES, DASHBOARD_I18N_KEYS, Dashboard, DashboardI18nService, DeclarativeForm, DeclarativeTable, DeclarativeTableCard, DeleteConfirmationDialog, EN_DEFAULTS, Favorites, LinkValue, MockCard, ResourceField, ResourceFormDialog, SanitizeHtmlPipe, SecretValue, ServiceStatusCard, TagListValue, VisitedServiceCard, WhatsNew, defineDashboardElementMethods };
1618
- export type { ButtonSettings, CardConfig, CardsType, CssRule, DashboardButtonsSettings, DashboardConfig, DashboardI18nKey, DashboardTranslations, DeleteResourceConfirmationConfig, FieldDefinition, FieldFilterDefinition, FormFieldChangeEvent, FormFieldDefinition, FormFieldErrors, GenericResource, IconDesignType, ModalSettings, MountCfg, PropertyField, ResourceFieldButtonClickEvent, ResourceFormConfig, RuleCondition, SectionConfig, ServiceStatusItem, ServiceStatusValue, TableCardButtonSettings, TableCardConfig, TableCardFormState, TableCardSearchConfig, TableConfig, TableFieldDefinition, TagSettings, TransformType, UiSettings, ValueRule };
1636
+ export type { ButtonSettings, CardConfig, CardsType, CssRule, DashboardButtonsSettings, DashboardConfig, DashboardI18nKey, DashboardTranslations, DeleteResourceConfirmationConfig, FieldDefinition, FieldFilterDefinition, FormFieldChangeEvent, FormFieldDefinition, FormFieldErrors, GenericResource, IconDesignType, LinkSettings, ModalSettings, MountCfg, PropertyField, ResourceFieldButtonClickEvent, ResourceFormConfig, RuleCondition, SectionConfig, ServiceStatusItem, ServiceStatusValue, TableCardButtonSettings, TableCardConfig, TableCardFormState, TableCardSearchConfig, TableConfig, TableFieldDefinition, TagSettings, TransformType, UiSettings, ValueRule };