@openmfp/ngx 1.2.0 → 1.4.0
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.
- package/fesm2022/openmfp-ngx.mjs +128 -22
- package/package.json +1 -1
- package/types/openmfp-ngx.d.ts +49 -4
package/fesm2022/openmfp-ngx.mjs
CHANGED
|
@@ -27,6 +27,7 @@ import '@ui5/webcomponents-icons/dist/add.js';
|
|
|
27
27
|
import '@ui5/webcomponents-icons/dist/delete.js';
|
|
28
28
|
import '@ui5/webcomponents-icons/dist/navigation-down-arrow.js';
|
|
29
29
|
import '@ui5/webcomponents-icons/dist/navigation-right-arrow.js';
|
|
30
|
+
import { InputIcon } from '@fundamental-ngx/ui5-webcomponents/input-icon';
|
|
30
31
|
import { Label } from '@fundamental-ngx/ui5-webcomponents/label';
|
|
31
32
|
import { Option } from '@fundamental-ngx/ui5-webcomponents/option';
|
|
32
33
|
import { Select } from '@fundamental-ngx/ui5-webcomponents/select';
|
|
@@ -1832,6 +1833,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImpor
|
|
|
1832
1833
|
], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.ShadowDom, schemas: [CUSTOM_ELEMENTS_SCHEMA], template: "<ui5-dialog\n data-testid=\"generic-table-card-delete-dialog\"\n [accessibleName]=\"config()?.title ?? 'Confirm Delete'\"\n [open]=\"open()\"\n (ui5BeforeClose)=\"cancelled.emit()\"\n>\n <div class=\"dialog__header\" slot=\"header\">\n <ui5-icon class=\"dialog__header-icon\" design=\"Critical\" name=\"alert\" />\n <ui5-title level=\"H5\">\n <span\n [innerHTML]=\"config()?.title ?? 'Confirm Delete' | sanitizeHtml\"\n ></span>\n </ui5-title>\n </div>\n @if (config()?.message || requiresConfirmation()) {\n <div class=\"dialog__body\">\n @if (config()?.message; as message) {\n <p class=\"dialog__message\" [innerHTML]=\"message | sanitizeHtml\"></p>\n }\n @if (requiresConfirmation()) {\n <p class=\"dialog__message\">\n <ui5-input\n class=\"dialog__confirmation-input\"\n data-testid=\"generic-table-card-delete-confirmation-input\"\n [formControl]=\"confirmationControl\"\n [placeholder]=\"\n config()?.confirmationPlaceholder ?? 'Type to confirm'\n \"\n [valueState]=\"\n confirmationControl.invalid && confirmationControl.dirty\n ? 'Negative'\n : 'None'\n \"\n />\n </p>\n }\n </div>\n }\n <div class=\"dialog__footer\" slot=\"footer\">\n <ui5-button\n data-testid=\"generic-table-card-delete-confirm\"\n design=\"Negative\"\n [disabled]=\"confirmDisabled()\"\n (click)=\"onConfirm()\"\n >\n {{ config()?.confirmLabel ?? 'Delete' }}\n </ui5-button>\n <ui5-button\n data-testid=\"generic-table-card-delete-cancel\"\n design=\"Transparent\"\n (click)=\"cancelled.emit()\"\n >\n {{ config()?.cancelLabel ?? 'Cancel' }}\n </ui5-button>\n </div>\n</ui5-dialog>\n", styles: [":host{display:contents}.dialog__header{display:flex;align-items:center;gap:.5rem;padding:.75rem 1rem}.dialog__header-icon{color:var(--sapCriticalColor)}.dialog__body{margin:-1rem;min-width:320px;padding:1rem}.dialog__message{margin:0;font-size:.875rem;max-width:320px}.dialog__message--critical{color:var(--sapCriticalElementColor)}.dialog__confirmation-input{display:block;width:100%;max-width:320px}.dialog__footer{display:flex;justify-content:space-between;align-items:center;gap:.5rem;padding:.5rem 1rem;margin:0 -1rem;width:100%}\n"] }]
|
|
1833
1834
|
}], ctorParameters: () => [], propDecorators: { open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }], config: [{ type: i0.Input, args: [{ isSignal: true, alias: "config", required: false }] }], confirmed: [{ type: i0.Output, args: ["confirmed"] }], cancelled: [{ type: i0.Output, args: ["cancelled"] }] } });
|
|
1834
1835
|
|
|
1836
|
+
function coerceBoolean(value) {
|
|
1837
|
+
if (typeof value === 'boolean')
|
|
1838
|
+
return value;
|
|
1839
|
+
if (typeof value === 'string') {
|
|
1840
|
+
return value.trim().toLowerCase() === 'true';
|
|
1841
|
+
}
|
|
1842
|
+
return false;
|
|
1843
|
+
}
|
|
1844
|
+
|
|
1835
1845
|
const FORBIDDEN_SEGMENTS = new Set(['__proto__', 'prototype', 'constructor']);
|
|
1836
1846
|
function setPropertyByPath(object, path, value) {
|
|
1837
1847
|
const segments = path.split('.').filter(Boolean);
|
|
@@ -1941,6 +1951,8 @@ class DeclarativeForm {
|
|
|
1941
1951
|
form;
|
|
1942
1952
|
collectionSeeds = signal({}, /* @ts-ignore */
|
|
1943
1953
|
...(ngDevMode ? [{ debugName: "collectionSeeds" }] : /* istanbul ignore next */ []));
|
|
1954
|
+
passwordVisible = signal({}, /* @ts-ignore */
|
|
1955
|
+
...(ngDevMode ? [{ debugName: "passwordVisible" }] : /* istanbul ignore next */ []));
|
|
1944
1956
|
fb = inject(FormBuilder);
|
|
1945
1957
|
constructor() {
|
|
1946
1958
|
this.form = this.fb.group({});
|
|
@@ -1977,6 +1989,47 @@ class DeclarativeForm {
|
|
|
1977
1989
|
});
|
|
1978
1990
|
}
|
|
1979
1991
|
}
|
|
1992
|
+
setSwitchValue($event, field) {
|
|
1993
|
+
const target = $event.target;
|
|
1994
|
+
const control = this.form.controls[field.name];
|
|
1995
|
+
const checked = coerceBoolean(target.checked);
|
|
1996
|
+
control.setValue(checked);
|
|
1997
|
+
control.markAsTouched();
|
|
1998
|
+
control.markAsDirty();
|
|
1999
|
+
this.formValueChange.emit(this.form.value);
|
|
2000
|
+
if (field.validation === 'onChange') {
|
|
2001
|
+
this.fieldChange.emit({
|
|
2002
|
+
fieldProperty: field.name,
|
|
2003
|
+
value: checked,
|
|
2004
|
+
});
|
|
2005
|
+
}
|
|
2006
|
+
}
|
|
2007
|
+
coerceChecked = coerceBoolean;
|
|
2008
|
+
isPasswordVisible(field) {
|
|
2009
|
+
return this.passwordVisible()[field.name] ?? false;
|
|
2010
|
+
}
|
|
2011
|
+
passwordInputType(field) {
|
|
2012
|
+
if (field.inputType !== 'Password') {
|
|
2013
|
+
return 'Text';
|
|
2014
|
+
}
|
|
2015
|
+
return this.isPasswordVisible(field) ? 'Text' : 'Password';
|
|
2016
|
+
}
|
|
2017
|
+
passwordToggleIcon(field) {
|
|
2018
|
+
return this.isPasswordVisible(field) ? 'hide' : 'show';
|
|
2019
|
+
}
|
|
2020
|
+
passwordToggleLabel(field) {
|
|
2021
|
+
return this.isPasswordVisible(field) ? 'Hide value' : 'Show value';
|
|
2022
|
+
}
|
|
2023
|
+
shouldShowPasswordToggle(field) {
|
|
2024
|
+
return field.inputType === 'Password' && (field.showPasswordToggle ?? true);
|
|
2025
|
+
}
|
|
2026
|
+
togglePasswordVisibility(field, event) {
|
|
2027
|
+
event.stopPropagation();
|
|
2028
|
+
this.passwordVisible.update((state) => ({
|
|
2029
|
+
...state,
|
|
2030
|
+
[field.name]: !this.isPasswordVisible(field),
|
|
2031
|
+
}));
|
|
2032
|
+
}
|
|
1980
2033
|
onCollectionValueChange(field, entries) {
|
|
1981
2034
|
const array = this.form.controls[field.name];
|
|
1982
2035
|
if (!array)
|
|
@@ -2017,6 +2070,7 @@ class DeclarativeForm {
|
|
|
2017
2070
|
}
|
|
2018
2071
|
clear() {
|
|
2019
2072
|
this.form.reset();
|
|
2073
|
+
this.passwordVisible.set({});
|
|
2020
2074
|
}
|
|
2021
2075
|
collectionEntries(field) {
|
|
2022
2076
|
return this.collectionSeeds()[field.name] ?? [];
|
|
@@ -2039,6 +2093,9 @@ class DeclarativeForm {
|
|
|
2039
2093
|
if (wantsCollection) {
|
|
2040
2094
|
this.form.addControl(field.name, this.fb.array([]));
|
|
2041
2095
|
}
|
|
2096
|
+
else if (field.inputType === 'Switch') {
|
|
2097
|
+
this.form.addControl(field.name, new FormControl(false));
|
|
2098
|
+
}
|
|
2042
2099
|
else {
|
|
2043
2100
|
this.form.addControl(field.name, new FormControl(''));
|
|
2044
2101
|
}
|
|
@@ -2073,7 +2130,13 @@ class DeclarativeForm {
|
|
|
2073
2130
|
seeds[field.name] = entries;
|
|
2074
2131
|
}
|
|
2075
2132
|
this.collectionSeeds.set(seeds);
|
|
2076
|
-
|
|
2133
|
+
const normalizedValues = { ...initialValues };
|
|
2134
|
+
for (const field of this.fields()) {
|
|
2135
|
+
if (field.inputType === 'Switch') {
|
|
2136
|
+
normalizedValues[field.name] = coerceBoolean(normalizedValues[field.name]);
|
|
2137
|
+
}
|
|
2138
|
+
}
|
|
2139
|
+
this.form.patchValue(normalizedValues, { emitEvent: false });
|
|
2077
2140
|
this.form.markAsPristine({ emitEvent: false });
|
|
2078
2141
|
this.form.updateValueAndValidity({ emitEvent: false });
|
|
2079
2142
|
}
|
|
@@ -2095,18 +2158,20 @@ class DeclarativeForm {
|
|
|
2095
2158
|
});
|
|
2096
2159
|
}
|
|
2097
2160
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: DeclarativeForm, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2098
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.2", type: DeclarativeForm, isStandalone: true, selector: "mfp-declarative-form", inputs: { fields: { classPropertyName: "fields", publicName: "fields", isSignal: true, isRequired: false, transformFunction: null }, initialValues: { classPropertyName: "initialValues", publicName: "initialValues", isSignal: true, isRequired: false, transformFunction: null }, fieldErrors: { classPropertyName: "fieldErrors", publicName: "fieldErrors", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { fieldChange: "fieldChange", formSubmit: "formSubmit", formValueChange: "formValueChange" }, ngImport: i0, template: "<section class=\"form\" data-testid=\"generic-form\" [formGroup]=\"form\">\n @for (field of fields(); track field.name) {\n @let control = form.controls[field.name];\n <div\n class=\"inputs\"\n [attr.data-testid]=\"'generic-form-field-container-' + field.name\"\n >\n <ui5-label\n show-colon\n [attr.data-testid]=\"'generic-form-field-label-' + field.name\"\n [attr.for]=\"'generic-form-field-input-' + field.name\"\n [required]=\"field.required\"\n >{{ field.label }}</ui5-label\n >\n @if (field.propertyCollection?.length) {\n <mfp-form-collection-field\n [attr.data-testid]=\"'generic-form-field-' + field.name\"\n [fields]=\"field.propertyCollection!\"\n [initialEntries]=\"collectionEntries(field)\"\n [label]=\"field.label\"\n (valueChange)=\"onCollectionValueChange(field, $event)\"\n />\n @if (getError(field.name); as error) {\n <div class=\"collection-error\" role=\"alert\">{{ error }}</div>\n }\n } @else if (field.values?.length) {\n <ui5-select\n class=\"input\"\n [attr.data-testid]=\"'generic-form-field-' + field.name\"\n [disabled]=\"field.disabled\"\n [id]=\"'generic-form-field-input-' + field.name\"\n [required]=\"field.required\"\n [value]=\"control.value\"\n [valueState]=\"getValueState(field.name)\"\n (blur)=\"onFieldBlur(field)\"\n (change)=\"setFormControlValue($event, field)\"\n (input)=\"setFormControlValue($event, field)\"\n >\n @if (getError(field.name); as error) {\n <div slot=\"valueStateMessage\">{{ error }}</div>\n }\n @for (value of [''].concat(field.values ?? []); track value) {\n <ui5-option\n [attr.data-testid]=\"\n 'generic-form-field-' +\n field.name +\n '-option-' +\n (value || 'empty')\n \"\n [selected]=\"value === control.value\"\n [value]=\"value\"\n >{{ value }}</ui5-option\n >\n }\n </ui5-select>\n } @else {\n <ui5-input\n class=\"input\"\n [attr.data-testid]=\"'generic-form-field-' + field.name\"\n [disabled]=\"field.disabled\"\n [id]=\"'generic-form-field-input-' + field.name\"\n [required]=\"field.required\"\n [value]=\"control.value\"\n [valueState]=\"getValueState(field.name)\"\n (blur)=\"onFieldBlur(field)\"\n (change)=\"setFormControlValue($event, field)\"\n (input)=\"setFormControlValue($event, field)\"\n >\n @if (getError(field.name); as error) {\n <div slot=\"valueStateMessage\">{{ error }}</div>\n }\n </ui5-input>\n }\n </div>\n }\n</section>\n", styles: [".form>div{display:flex;flex-direction:column;justify-content:space-evenly;align-items:flex-start;margin-bottom:.5rem;width:100%}.input{width:100%}.collection-error{color:var(--sapNegativeColor, #b00);font-size:.75rem;margin-top:.25rem}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "component", type: Input, selector: "ui5-input, [ui5-input]", inputs: ["accessibleDescription", "accessibleDescriptionRef", "accessibleName", "accessibleNameRef", "disabled", "filter", "maxlength", "name", "noTypeahead", "open", "placeholder", "readonly", "required", "showClearIcon", "showSuggestions", "type", "value", "valueState"], outputs: ["ui5Change", "ui5Close", "ui5Input", "ui5Open", "ui5Select", "ui5SelectionChange"], exportAs: ["ui5Input"] }, { kind: "component", type: Label, selector: "ui5-label, [ui5-label]", inputs: ["for", "required", "showColon", "wrappingType"], exportAs: ["ui5Label"] }, { kind: "component", type: Select, selector: "ui5-select, [ui5-select]", inputs: ["accessibleDescription", "accessibleDescriptionRef", "accessibleName", "accessibleNameRef", "disabled", "name", "readonly", "required", "textSeparator", "tooltip", "value", "valueState"], outputs: ["ui5Change", "ui5Close", "ui5LiveChange", "ui5Open"], exportAs: ["ui5Select"] }, { kind: "component", type: Option, selector: "ui5-option, [ui5-option]", inputs: ["additionalText", "icon", "selected", "tooltip", "value"], outputs: ["ui5Click"], exportAs: ["ui5Option"] }, { kind: "component", type: FormCollectionField, selector: "mfp-form-collection-field", inputs: ["fields", "label", "initialEntries"], outputs: ["valueChange"] }], encapsulation: i0.ViewEncapsulation.ShadowDom });
|
|
2161
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.2", type: DeclarativeForm, isStandalone: true, selector: "mfp-declarative-form", inputs: { fields: { classPropertyName: "fields", publicName: "fields", isSignal: true, isRequired: false, transformFunction: null }, initialValues: { classPropertyName: "initialValues", publicName: "initialValues", isSignal: true, isRequired: false, transformFunction: null }, fieldErrors: { classPropertyName: "fieldErrors", publicName: "fieldErrors", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { fieldChange: "fieldChange", formSubmit: "formSubmit", formValueChange: "formValueChange" }, ngImport: i0, template: "<section class=\"form\" data-testid=\"generic-form\" [formGroup]=\"form\">\n @for (field of fields(); track field.name) {\n @let control = form.controls[field.name];\n <div\n class=\"inputs\"\n [attr.data-testid]=\"'generic-form-field-container-' + field.name\"\n [class.inputs--switch]=\"field.inputType === 'Switch'\"\n >\n <ui5-label\n show-colon\n [attr.data-testid]=\"'generic-form-field-label-' + field.name\"\n [attr.for]=\"\n field.inputType === 'Switch'\n ? null\n : 'generic-form-field-input-' + field.name\n \"\n [required]=\"field.required\"\n >{{ field.label }}</ui5-label\n >\n @if (field.propertyCollection?.length) {\n <mfp-form-collection-field\n [attr.data-testid]=\"'generic-form-field-' + field.name\"\n [fields]=\"field.propertyCollection!\"\n [initialEntries]=\"collectionEntries(field)\"\n [label]=\"field.label\"\n (valueChange)=\"onCollectionValueChange(field, $event)\"\n />\n @if (getError(field.name); as error) {\n <div class=\"collection-error\" role=\"alert\">{{ error }}</div>\n }\n } @else if (field.values?.length) {\n <ui5-select\n class=\"input\"\n [attr.data-testid]=\"'generic-form-field-' + field.name\"\n [disabled]=\"field.disabled\"\n [id]=\"'generic-form-field-input-' + field.name\"\n [required]=\"field.required\"\n [value]=\"control.value\"\n [valueState]=\"getValueState(field.name)\"\n (blur)=\"onFieldBlur(field)\"\n (change)=\"setFormControlValue($event, field)\"\n (input)=\"setFormControlValue($event, field)\"\n >\n @if (getError(field.name); as error) {\n <div slot=\"valueStateMessage\">{{ error }}</div>\n }\n @for (value of [''].concat(field.values ?? []); track value) {\n <ui5-option\n [attr.data-testid]=\"\n 'generic-form-field-' +\n field.name +\n '-option-' +\n (value || 'empty')\n \"\n [selected]=\"value === control.value\"\n [value]=\"value\"\n >{{ value }}</ui5-option\n >\n }\n </ui5-select>\n } @else if (field.inputType === 'Switch') {\n <ui5-switch\n class=\"switch\"\n [accessibleName]=\"field.label\"\n [attr.data-testid]=\"'generic-form-field-' + field.name\"\n [checked]=\"coerceChecked(control.value)\"\n [disabled]=\"field.disabled\"\n (blur)=\"onFieldBlur(field)\"\n (ui5Change)=\"setSwitchValue($event, field)\"\n />\n @if (getError(field.name); as error) {\n <div class=\"field-error\" role=\"alert\">{{ error }}</div>\n }\n } @else {\n <ui5-input\n class=\"input\"\n [attr.data-testid]=\"'generic-form-field-' + field.name\"\n [attr.title]=\"field.placeholder || null\"\n [disabled]=\"field.disabled\"\n [id]=\"'generic-form-field-input-' + field.name\"\n [placeholder]=\"field.placeholder ?? ''\"\n [required]=\"field.required\"\n [type]=\"passwordInputType(field)\"\n [value]=\"control.value\"\n [valueState]=\"getValueState(field.name)\"\n (blur)=\"onFieldBlur(field)\"\n (change)=\"setFormControlValue($event, field)\"\n (input)=\"setFormControlValue($event, field)\"\n >\n @if (shouldShowPasswordToggle(field)) {\n <ui5-input-icon\n slot=\"icon\"\n [accessibleName]=\"passwordToggleLabel(field)\"\n [attr.data-testid]=\"\n 'generic-form-field-' + field.name + '-password-toggle'\n \"\n [name]=\"passwordToggleIcon(field)\"\n (ui5Click)=\"togglePasswordVisibility(field, $event)\"\n />\n }\n @if (getError(field.name); as error) {\n <div slot=\"valueStateMessage\">{{ error }}</div>\n }\n </ui5-input>\n }\n @if (field.hint && !field.propertyCollection?.length) {\n <div\n class=\"field-hint\"\n [attr.data-testid]=\"'generic-form-field-hint-' + field.name\"\n >\n {{ field.hint }}\n </div>\n }\n </div>\n }\n</section>\n", styles: [".form>div{display:flex;flex-direction:column;justify-content:space-evenly;align-items:flex-start;margin-bottom:.5rem;width:100%}.input{width:100%}.inputs--switch{flex-direction:row;align-items:center;gap:.75rem}.switch{flex-shrink:0}.inputs--switch .field-hint{flex-basis:100%}.collection-error,.field-error{color:var(--sapNegativeColor, #b00);font-size:.75rem;margin-top:.25rem}.inputs--switch .field-error{flex-basis:100%}.field-hint{color:var(--sapContent_LabelColor, #6a6d70);font-size:.75rem;line-height:1.4;margin-top:.25rem;width:100%}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "component", type: Input, selector: "ui5-input, [ui5-input]", inputs: ["accessibleDescription", "accessibleDescriptionRef", "accessibleName", "accessibleNameRef", "disabled", "filter", "maxlength", "name", "noTypeahead", "open", "placeholder", "readonly", "required", "showClearIcon", "showSuggestions", "type", "value", "valueState"], outputs: ["ui5Change", "ui5Close", "ui5Input", "ui5Open", "ui5Select", "ui5SelectionChange"], exportAs: ["ui5Input"] }, { kind: "component", type: InputIcon, selector: "ui5-input-icon, [ui5-input-icon]", inputs: ["accessibleName", "name"], outputs: ["ui5Click"], exportAs: ["ui5InputIcon"] }, { kind: "component", type: Label, selector: "ui5-label, [ui5-label]", inputs: ["for", "required", "showColon", "wrappingType"], exportAs: ["ui5Label"] }, { kind: "component", type: Select, selector: "ui5-select, [ui5-select]", inputs: ["accessibleDescription", "accessibleDescriptionRef", "accessibleName", "accessibleNameRef", "disabled", "name", "readonly", "required", "textSeparator", "tooltip", "value", "valueState"], outputs: ["ui5Change", "ui5Close", "ui5LiveChange", "ui5Open"], exportAs: ["ui5Select"] }, { kind: "component", type: Option, selector: "ui5-option, [ui5-option]", inputs: ["additionalText", "icon", "selected", "tooltip", "value"], outputs: ["ui5Click"], exportAs: ["ui5Option"] }, { kind: "component", type: Switch, selector: "ui5-switch, [ui5-switch]", inputs: ["accessibleName", "accessibleNameRef", "checked", "design", "disabled", "name", "readonly", "required", "textOff", "textOn", "tooltip", "value"], outputs: ["ui5Change"], exportAs: ["ui5Switch"] }, { kind: "component", type: FormCollectionField, selector: "mfp-form-collection-field", inputs: ["fields", "label", "initialEntries"], outputs: ["valueChange"] }], encapsulation: i0.ViewEncapsulation.ShadowDom });
|
|
2099
2162
|
}
|
|
2100
2163
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: DeclarativeForm, decorators: [{
|
|
2101
2164
|
type: Component,
|
|
2102
2165
|
args: [{ selector: 'mfp-declarative-form', imports: [
|
|
2103
2166
|
ReactiveFormsModule,
|
|
2104
2167
|
Input,
|
|
2168
|
+
InputIcon,
|
|
2105
2169
|
Label,
|
|
2106
2170
|
Select,
|
|
2107
2171
|
Option,
|
|
2172
|
+
Switch,
|
|
2108
2173
|
FormCollectionField,
|
|
2109
|
-
], encapsulation: ViewEncapsulation.ShadowDom, template: "<section class=\"form\" data-testid=\"generic-form\" [formGroup]=\"form\">\n @for (field of fields(); track field.name) {\n @let control = form.controls[field.name];\n <div\n class=\"inputs\"\n [attr.data-testid]=\"'generic-form-field-container-' + field.name\"\n >\n <ui5-label\n show-colon\n [attr.data-testid]=\"'generic-form-field-label-' + field.name\"\n [attr.for]=\"'generic-form-field-input-' + field.name\"\n [required]=\"field.required\"\n >{{ field.label }}</ui5-label\n >\n @if (field.propertyCollection?.length) {\n <mfp-form-collection-field\n [attr.data-testid]=\"'generic-form-field-' + field.name\"\n [fields]=\"field.propertyCollection!\"\n [initialEntries]=\"collectionEntries(field)\"\n [label]=\"field.label\"\n (valueChange)=\"onCollectionValueChange(field, $event)\"\n />\n @if (getError(field.name); as error) {\n <div class=\"collection-error\" role=\"alert\">{{ error }}</div>\n }\n } @else if (field.values?.length) {\n <ui5-select\n class=\"input\"\n [attr.data-testid]=\"'generic-form-field-' + field.name\"\n [disabled]=\"field.disabled\"\n [id]=\"'generic-form-field-input-' + field.name\"\n [required]=\"field.required\"\n [value]=\"control.value\"\n [valueState]=\"getValueState(field.name)\"\n (blur)=\"onFieldBlur(field)\"\n (change)=\"setFormControlValue($event, field)\"\n (input)=\"setFormControlValue($event, field)\"\n >\n @if (getError(field.name); as error) {\n <div slot=\"valueStateMessage\">{{ error }}</div>\n }\n @for (value of [''].concat(field.values ?? []); track value) {\n <ui5-option\n [attr.data-testid]=\"\n 'generic-form-field-' +\n field.name +\n '-option-' +\n (value || 'empty')\n \"\n [selected]=\"value === control.value\"\n [value]=\"value\"\n >{{ value }}</ui5-option\n >\n }\n </ui5-select>\n } @else {\n <ui5-input\n class=\"input\"\n [attr.data-testid]=\"'generic-form-field-' + field.name\"\n [disabled]=\"field.disabled\"\n [id]=\"'generic-form-field-input-' + field.name\"\n [required]=\"field.required\"\n [value]=\"control.value\"\n [valueState]=\"getValueState(field.name)\"\n (blur)=\"onFieldBlur(field)\"\n (change)=\"setFormControlValue($event, field)\"\n (input)=\"setFormControlValue($event, field)\"\n >\n @if (getError(field.name); as error) {\n <div slot=\"valueStateMessage\">{{ error }}</div>\n }\n </ui5-input>\n }\n </div>\n }\n</section>\n", styles: [".form>div{display:flex;flex-direction:column;justify-content:space-evenly;align-items:flex-start;margin-bottom:.5rem;width:100%}.input{width:100%}.collection-error{color:var(--sapNegativeColor, #b00);font-size:.75rem;margin-top:.25rem}\n"] }]
|
|
2174
|
+
], encapsulation: ViewEncapsulation.ShadowDom, template: "<section class=\"form\" data-testid=\"generic-form\" [formGroup]=\"form\">\n @for (field of fields(); track field.name) {\n @let control = form.controls[field.name];\n <div\n class=\"inputs\"\n [attr.data-testid]=\"'generic-form-field-container-' + field.name\"\n [class.inputs--switch]=\"field.inputType === 'Switch'\"\n >\n <ui5-label\n show-colon\n [attr.data-testid]=\"'generic-form-field-label-' + field.name\"\n [attr.for]=\"\n field.inputType === 'Switch'\n ? null\n : 'generic-form-field-input-' + field.name\n \"\n [required]=\"field.required\"\n >{{ field.label }}</ui5-label\n >\n @if (field.propertyCollection?.length) {\n <mfp-form-collection-field\n [attr.data-testid]=\"'generic-form-field-' + field.name\"\n [fields]=\"field.propertyCollection!\"\n [initialEntries]=\"collectionEntries(field)\"\n [label]=\"field.label\"\n (valueChange)=\"onCollectionValueChange(field, $event)\"\n />\n @if (getError(field.name); as error) {\n <div class=\"collection-error\" role=\"alert\">{{ error }}</div>\n }\n } @else if (field.values?.length) {\n <ui5-select\n class=\"input\"\n [attr.data-testid]=\"'generic-form-field-' + field.name\"\n [disabled]=\"field.disabled\"\n [id]=\"'generic-form-field-input-' + field.name\"\n [required]=\"field.required\"\n [value]=\"control.value\"\n [valueState]=\"getValueState(field.name)\"\n (blur)=\"onFieldBlur(field)\"\n (change)=\"setFormControlValue($event, field)\"\n (input)=\"setFormControlValue($event, field)\"\n >\n @if (getError(field.name); as error) {\n <div slot=\"valueStateMessage\">{{ error }}</div>\n }\n @for (value of [''].concat(field.values ?? []); track value) {\n <ui5-option\n [attr.data-testid]=\"\n 'generic-form-field-' +\n field.name +\n '-option-' +\n (value || 'empty')\n \"\n [selected]=\"value === control.value\"\n [value]=\"value\"\n >{{ value }}</ui5-option\n >\n }\n </ui5-select>\n } @else if (field.inputType === 'Switch') {\n <ui5-switch\n class=\"switch\"\n [accessibleName]=\"field.label\"\n [attr.data-testid]=\"'generic-form-field-' + field.name\"\n [checked]=\"coerceChecked(control.value)\"\n [disabled]=\"field.disabled\"\n (blur)=\"onFieldBlur(field)\"\n (ui5Change)=\"setSwitchValue($event, field)\"\n />\n @if (getError(field.name); as error) {\n <div class=\"field-error\" role=\"alert\">{{ error }}</div>\n }\n } @else {\n <ui5-input\n class=\"input\"\n [attr.data-testid]=\"'generic-form-field-' + field.name\"\n [attr.title]=\"field.placeholder || null\"\n [disabled]=\"field.disabled\"\n [id]=\"'generic-form-field-input-' + field.name\"\n [placeholder]=\"field.placeholder ?? ''\"\n [required]=\"field.required\"\n [type]=\"passwordInputType(field)\"\n [value]=\"control.value\"\n [valueState]=\"getValueState(field.name)\"\n (blur)=\"onFieldBlur(field)\"\n (change)=\"setFormControlValue($event, field)\"\n (input)=\"setFormControlValue($event, field)\"\n >\n @if (shouldShowPasswordToggle(field)) {\n <ui5-input-icon\n slot=\"icon\"\n [accessibleName]=\"passwordToggleLabel(field)\"\n [attr.data-testid]=\"\n 'generic-form-field-' + field.name + '-password-toggle'\n \"\n [name]=\"passwordToggleIcon(field)\"\n (ui5Click)=\"togglePasswordVisibility(field, $event)\"\n />\n }\n @if (getError(field.name); as error) {\n <div slot=\"valueStateMessage\">{{ error }}</div>\n }\n </ui5-input>\n }\n @if (field.hint && !field.propertyCollection?.length) {\n <div\n class=\"field-hint\"\n [attr.data-testid]=\"'generic-form-field-hint-' + field.name\"\n >\n {{ field.hint }}\n </div>\n }\n </div>\n }\n</section>\n", styles: [".form>div{display:flex;flex-direction:column;justify-content:space-evenly;align-items:flex-start;margin-bottom:.5rem;width:100%}.input{width:100%}.inputs--switch{flex-direction:row;align-items:center;gap:.75rem}.switch{flex-shrink:0}.inputs--switch .field-hint{flex-basis:100%}.collection-error,.field-error{color:var(--sapNegativeColor, #b00);font-size:.75rem;margin-top:.25rem}.inputs--switch .field-error{flex-basis:100%}.field-hint{color:var(--sapContent_LabelColor, #6a6d70);font-size:.75rem;line-height:1.4;margin-top:.25rem;width:100%}\n"] }]
|
|
2110
2175
|
}], ctorParameters: () => [], propDecorators: { fields: [{ type: i0.Input, args: [{ isSignal: true, alias: "fields", required: false }] }], initialValues: [{ type: i0.Input, args: [{ isSignal: true, alias: "initialValues", required: false }] }], fieldErrors: [{ type: i0.Input, args: [{ isSignal: true, alias: "fieldErrors", required: false }] }], fieldChange: [{ type: i0.Output, args: ["fieldChange"] }], formSubmit: [{ type: i0.Output, args: ["formSubmit"] }], formValueChange: [{ type: i0.Output, args: ["formValueChange"] }] } });
|
|
2111
2176
|
|
|
2112
2177
|
class ResourceFormDialog {
|
|
@@ -2230,6 +2295,48 @@ const decodeBase64 = (base64) => {
|
|
|
2230
2295
|
throw new Error('Failed to decode Base64 string');
|
|
2231
2296
|
}
|
|
2232
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
|
+
};
|
|
2233
2340
|
|
|
2234
2341
|
function getFieldValue(field, resource) {
|
|
2235
2342
|
if (resource) {
|
|
@@ -2331,16 +2438,18 @@ class LinkValue {
|
|
|
2331
2438
|
...(ngDevMode ? [{ debugName: "urlValue" }] : /* istanbul ignore next */ []));
|
|
2332
2439
|
testId = input('link-value-link', /* @ts-ignore */
|
|
2333
2440
|
...(ngDevMode ? [{ debugName: "testId" }] : /* istanbul ignore next */ []));
|
|
2441
|
+
displayValue = input(/* @ts-ignore */
|
|
2442
|
+
...(ngDevMode ? [undefined, { debugName: "displayValue" }] : /* istanbul ignore next */ []));
|
|
2334
2443
|
stopPropagation(event) {
|
|
2335
2444
|
event.stopPropagation();
|
|
2336
2445
|
}
|
|
2337
2446
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: LinkValue, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2338
|
-
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 >
|
|
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 });
|
|
2339
2448
|
}
|
|
2340
2449
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: LinkValue, decorators: [{
|
|
2341
2450
|
type: Component,
|
|
2342
|
-
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 >
|
|
2343
|
-
}], 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 }] }] } });
|
|
2344
2453
|
|
|
2345
2454
|
/**
|
|
2346
2455
|
* Read-only display for a `propertyCollection` field. Renders the resolved
|
|
@@ -2483,8 +2592,17 @@ class ResourceField {
|
|
|
2483
2592
|
...(ngDevMode ? [{ debugName: "displayValue" }] : /* istanbul ignore next */ []));
|
|
2484
2593
|
isBoolLike = computed(() => this.boolValue() !== undefined, /* @ts-ignore */
|
|
2485
2594
|
...(ngDevMode ? [{ debugName: "isBoolLike" }] : /* istanbul ignore next */ []));
|
|
2486
|
-
|
|
2487
|
-
...(ngDevMode ? [{ debugName: "
|
|
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 */ []));
|
|
2488
2606
|
testId = computed(() => `resource-field-${this.fieldDefinition().property}`, /* @ts-ignore */
|
|
2489
2607
|
...(ngDevMode ? [{ debugName: "testId" }] : /* istanbul ignore next */ []));
|
|
2490
2608
|
buttonDisabled = computed(() => this.resource()?.isAvailable === false, /* @ts-ignore */
|
|
@@ -2552,18 +2670,6 @@ class ResourceField {
|
|
|
2552
2670
|
}
|
|
2553
2671
|
return [];
|
|
2554
2672
|
}
|
|
2555
|
-
checkValidUrl(value) {
|
|
2556
|
-
if (!value) {
|
|
2557
|
-
return false;
|
|
2558
|
-
}
|
|
2559
|
-
try {
|
|
2560
|
-
new URL(value);
|
|
2561
|
-
return true;
|
|
2562
|
-
}
|
|
2563
|
-
catch {
|
|
2564
|
-
return false;
|
|
2565
|
-
}
|
|
2566
|
-
}
|
|
2567
2673
|
copyValue(event) {
|
|
2568
2674
|
event.stopPropagation();
|
|
2569
2675
|
navigator.clipboard.writeText(this.value() || '').then(() => {
|
|
@@ -2585,7 +2691,7 @@ class ResourceField {
|
|
|
2585
2691
|
});
|
|
2586
2692
|
}
|
|
2587
2693
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: ResourceField, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2588
|
-
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' &&
|
|
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 > \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 });
|
|
2589
2695
|
}
|
|
2590
2696
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: ResourceField, decorators: [{
|
|
2591
2697
|
type: Component,
|
|
@@ -2603,7 +2709,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImpor
|
|
|
2603
2709
|
forwardRef(() => ResourceCollectionField),
|
|
2604
2710
|
], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.ShadowDom, host: {
|
|
2605
2711
|
'[class.resource-field--collection]': 'isCollection()',
|
|
2606
|
-
}, 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' &&
|
|
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 > \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"] }]
|
|
2607
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"] }] } });
|
|
2608
2714
|
|
|
2609
2715
|
const processGroupFields = (fields) => {
|
package/package.json
CHANGED
package/types/openmfp-ngx.d.ts
CHANGED
|
@@ -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. */
|
|
@@ -526,6 +542,22 @@ interface FormFieldDefinition {
|
|
|
526
542
|
values?: string[];
|
|
527
543
|
/** When `true`, the field is disabled (not interactive). */
|
|
528
544
|
disabled?: boolean;
|
|
545
|
+
/** UI5 input type rendered by `mfp-declarative-form`. */
|
|
546
|
+
inputType?: 'Text' | 'Password' | 'Switch';
|
|
547
|
+
/**
|
|
548
|
+
* When `inputType` is `Password`, renders a show/hide icon inside the input.
|
|
549
|
+
* Defaults to `true`; set to `false` to keep the field masked without a toggle.
|
|
550
|
+
*/
|
|
551
|
+
showPasswordToggle?: boolean;
|
|
552
|
+
/** UI5 input placeholder (functional, not example values). */
|
|
553
|
+
placeholder?: string;
|
|
554
|
+
/** Persistent help text below the control; never submitted with the form. */
|
|
555
|
+
hint?: string;
|
|
556
|
+
/**
|
|
557
|
+
* When `true`, the host should omit this field from read queries and skip
|
|
558
|
+
* empty values on edit submit (e.g. write-only secrets).
|
|
559
|
+
*/
|
|
560
|
+
writeOnly?: boolean;
|
|
529
561
|
/** Controls when `fieldChange` is emitted for this field. When omitted, `fieldChange` is never emitted. */
|
|
530
562
|
validation?: 'onBlur' | 'onChange';
|
|
531
563
|
/**
|
|
@@ -553,6 +585,8 @@ interface FormFieldChangeEvent {
|
|
|
553
585
|
/** Map of field names to their current validation error messages (`null` = no error). */
|
|
554
586
|
type FormFieldErrors = Record<string, string | null>;
|
|
555
587
|
|
|
588
|
+
declare function coerceBoolean(value: unknown): boolean;
|
|
589
|
+
|
|
556
590
|
declare class DeclarativeForm<T extends GenericResource> {
|
|
557
591
|
readonly fields: _angular_core.InputSignal<FormFieldDefinition[]>;
|
|
558
592
|
readonly initialValues: _angular_core.InputSignal<T>;
|
|
@@ -562,9 +596,18 @@ declare class DeclarativeForm<T extends GenericResource> {
|
|
|
562
596
|
readonly formValueChange: _angular_core.OutputEmitterRef<Record<string, unknown>>;
|
|
563
597
|
readonly form: FormGroup;
|
|
564
598
|
protected readonly collectionSeeds: _angular_core.WritableSignal<Record<string, Record<string, unknown>[]>>;
|
|
599
|
+
private readonly passwordVisible;
|
|
565
600
|
private readonly fb;
|
|
566
601
|
constructor();
|
|
567
602
|
setFormControlValue($event: Event, field: FormFieldDefinition): void;
|
|
603
|
+
setSwitchValue($event: Event, field: FormFieldDefinition): void;
|
|
604
|
+
protected readonly coerceChecked: typeof coerceBoolean;
|
|
605
|
+
isPasswordVisible(field: FormFieldDefinition): boolean;
|
|
606
|
+
passwordInputType(field: FormFieldDefinition): 'Text' | 'Password';
|
|
607
|
+
passwordToggleIcon(field: FormFieldDefinition): 'hide' | 'show';
|
|
608
|
+
passwordToggleLabel(field: FormFieldDefinition): string;
|
|
609
|
+
shouldShowPasswordToggle(field: FormFieldDefinition): boolean;
|
|
610
|
+
togglePasswordVisibility(field: FormFieldDefinition, event: Event): void;
|
|
568
611
|
onCollectionValueChange(field: FormFieldDefinition, entries: Record<string, unknown>[]): void;
|
|
569
612
|
getError(name: string): string | null;
|
|
570
613
|
getValueState(name: string): 'None' | 'Negative';
|
|
@@ -1468,7 +1511,9 @@ declare class ResourceField<T extends GenericResource, F extends FieldDefinition
|
|
|
1468
1511
|
}>;
|
|
1469
1512
|
displayValue: _angular_core.Signal<string>;
|
|
1470
1513
|
isBoolLike: _angular_core.Signal<boolean>;
|
|
1471
|
-
|
|
1514
|
+
linkSettings: _angular_core.Signal<_openmfp_ngx.LinkSettings | undefined>;
|
|
1515
|
+
linkHref: _angular_core.Signal<string | undefined>;
|
|
1516
|
+
linkText: _angular_core.Signal<string | undefined>;
|
|
1472
1517
|
testId: _angular_core.Signal<string>;
|
|
1473
1518
|
buttonDisabled: _angular_core.Signal<boolean>;
|
|
1474
1519
|
buttonAccessibleName: _angular_core.Signal<string>;
|
|
@@ -1483,7 +1528,6 @@ declare class ResourceField<T extends GenericResource, F extends FieldDefinition
|
|
|
1483
1528
|
private normalizeBoolean;
|
|
1484
1529
|
private normalizeString;
|
|
1485
1530
|
private normalizeTagsArray;
|
|
1486
|
-
private checkValidUrl;
|
|
1487
1531
|
copyValue(event: Event): void;
|
|
1488
1532
|
protected buttonClicked(event: MouseEvent): void;
|
|
1489
1533
|
static ɵfac: _angular_core.ɵɵFactoryDeclaration<ResourceField<any, any>, never>;
|
|
@@ -1506,9 +1550,10 @@ declare class BooleanValue {
|
|
|
1506
1550
|
declare class LinkValue {
|
|
1507
1551
|
urlValue: _angular_core.InputSignal<string>;
|
|
1508
1552
|
testId: _angular_core.InputSignal<string>;
|
|
1553
|
+
displayValue: _angular_core.InputSignal<string | undefined>;
|
|
1509
1554
|
stopPropagation(event: Event): void;
|
|
1510
1555
|
static ɵfac: _angular_core.ɵɵFactoryDeclaration<LinkValue, never>;
|
|
1511
|
-
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>;
|
|
1512
1557
|
}
|
|
1513
1558
|
|
|
1514
1559
|
declare class SecretValue {
|
|
@@ -1588,4 +1633,4 @@ declare class MockCard {
|
|
|
1588
1633
|
}
|
|
1589
1634
|
|
|
1590
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 };
|
|
1591
|
-
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 };
|