@openmfp/ngx 0.6.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.
@@ -0,0 +1,1188 @@
1
+ import jsonpath from 'jsonpath';
2
+ import * as i0 from '@angular/core';
3
+ import { input, computed, CUSTOM_ELEMENTS_SCHEMA, ChangeDetectionStrategy, Component, output, signal, ViewEncapsulation, inject, effect, viewChild, Injector, afterNextRender, reflectComponentType, isStandalone, isDevMode, ViewContainerRef, Renderer2, model, ElementRef, linkedSignal } from '@angular/core';
4
+ import { Icon } from '@fundamental-ngx/ui5-webcomponents/icon';
5
+ import { Link } from '@fundamental-ngx/ui5-webcomponents/link';
6
+ import { Button } from '@fundamental-ngx/ui5-webcomponents/button';
7
+ import '@ui5/webcomponents-icons/dist/AllIcons.js';
8
+ import { IllustratedMessage } from '@fundamental-ngx/ui5-webcomponents-fiori';
9
+ import { Option } from '@fundamental-ngx/ui5-webcomponents/option';
10
+ import { Select } from '@fundamental-ngx/ui5-webcomponents/select';
11
+ import { Table } from '@fundamental-ngx/ui5-webcomponents/table';
12
+ import { TableCell } from '@fundamental-ngx/ui5-webcomponents/table-cell';
13
+ import { TableGrowing } from '@fundamental-ngx/ui5-webcomponents/table-growing';
14
+ import { TableHeaderCell } from '@fundamental-ngx/ui5-webcomponents/table-header-cell';
15
+ import { TableHeaderRow } from '@fundamental-ngx/ui5-webcomponents/table-header-row';
16
+ import { TableRow } from '@fundamental-ngx/ui5-webcomponents/table-row';
17
+ import '@ui5/webcomponents-fiori/dist/illustrations/NoData.js';
18
+ import * as i1 from '@angular/forms';
19
+ import { FormBuilder, FormControl, ReactiveFormsModule } from '@angular/forms';
20
+ import { Input } from '@fundamental-ngx/ui5-webcomponents/input';
21
+ import { Label } from '@fundamental-ngx/ui5-webcomponents/label';
22
+ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
23
+ import { Dialog } from '@fundamental-ngx/ui5-webcomponents/dialog';
24
+ import { Title } from '@fundamental-ngx/ui5-webcomponents/title';
25
+ import '@ui5/webcomponents-icons/dist/add.js';
26
+ import '@ui5/webcomponents-icons/dist/search.js';
27
+ import { debounceTime } from 'rxjs';
28
+ import { CheckBox } from '@fundamental-ngx/ui5-webcomponents/check-box';
29
+ import { Menu } from '@fundamental-ngx/ui5-webcomponents/menu';
30
+ import { MenuItem } from '@fundamental-ngx/ui5-webcomponents/menu-item';
31
+ import { MenuSeparator } from '@fundamental-ngx/ui5-webcomponents/menu-separator';
32
+ import { Text } from '@fundamental-ngx/ui5-webcomponents/text';
33
+ import '@ui5/webcomponents-icons/dist/action-settings.js';
34
+ import '@ui5/webcomponents-icons/dist/menu2.js';
35
+ import { GridstackComponent, GridstackItemComponent } from 'gridstack/dist/angular';
36
+ import { Card } from '@fundamental-ngx/ui5-webcomponents/card';
37
+ import { CardHeader } from '@fundamental-ngx/ui5-webcomponents/card-header';
38
+ import { List } from '@fundamental-ngx/ui5-webcomponents/list';
39
+ import { ListItemStandard } from '@fundamental-ngx/ui5-webcomponents/list-item-standard';
40
+
41
+ const processGroupFields = (fields) => {
42
+ return combineGroupFields(fields);
43
+ };
44
+ const collectGroupFields = (fields) => {
45
+ return fields.reduce((acc, f) => {
46
+ if (!f.group?.name) {
47
+ return acc;
48
+ }
49
+ const key = f.group.name;
50
+ if (!acc[key]) {
51
+ acc[key] = [];
52
+ }
53
+ // Strip group information from the field when adding to the fields array
54
+ const { group, ...fieldWithoutGroup } = f;
55
+ acc[key].push(fieldWithoutGroup);
56
+ return acc;
57
+ }, {});
58
+ };
59
+ const combineGroupFields = (fields) => {
60
+ const seenGroup = new Set();
61
+ const groupFields = collectGroupFields(fields);
62
+ const result = [];
63
+ fields.forEach((f) => {
64
+ if (!f.group?.name) {
65
+ result.push(f);
66
+ return;
67
+ }
68
+ const key = f.group.name;
69
+ if (seenGroup.has(key)) {
70
+ return;
71
+ }
72
+ seenGroup.add(key);
73
+ const grouped = {
74
+ ...f,
75
+ group: {
76
+ ...f.group,
77
+ fields: groupFields[key],
78
+ },
79
+ };
80
+ result.push(grouped);
81
+ });
82
+ return result;
83
+ };
84
+
85
+ const getResourceValueByJsonPath = (resource, field) => {
86
+ const property = field.jsonPathExpression || field.property;
87
+ if (!property) {
88
+ return undefined;
89
+ }
90
+ if (property instanceof Array) {
91
+ console.error(`Property defined as an array: ${JSON.stringify(property)}, provide "jsonPathExpression" field to properly ready resource value`);
92
+ return undefined;
93
+ }
94
+ const prefix = property.startsWith('$.') ? '' : '$.';
95
+ const queryResult = jsonpath.query(resource, `${prefix}${property}`);
96
+ const value = queryResult.length ? queryResult[0] : undefined;
97
+ if (value && field.propertyField) {
98
+ return executeTransform(value[field.propertyField.key], field.propertyField.transform);
99
+ }
100
+ return value;
101
+ };
102
+ const executeTransform = (value, transform) => {
103
+ if (value == null || transform == null || !transform.length)
104
+ return value;
105
+ return transform.reduce((acc, t) => {
106
+ if (acc == null)
107
+ return acc;
108
+ switch (t) {
109
+ case 'uppercase':
110
+ return acc.toUpperCase();
111
+ case 'lowercase':
112
+ return acc.toLowerCase();
113
+ case 'capitalize': {
114
+ const str = String(acc);
115
+ return str.length ? str.charAt(0).toUpperCase() + str.slice(1) : str;
116
+ }
117
+ case 'decode': {
118
+ try {
119
+ return decodeBase64(acc);
120
+ }
121
+ catch {
122
+ return acc;
123
+ }
124
+ }
125
+ case 'encode': {
126
+ try {
127
+ return encodeBase64(acc);
128
+ }
129
+ catch {
130
+ return acc;
131
+ }
132
+ }
133
+ default:
134
+ return acc;
135
+ }
136
+ }, value);
137
+ };
138
+ const encodeBase64 = (str) => {
139
+ try {
140
+ const utf8Bytes = new TextEncoder().encode(str);
141
+ const binaryString = Array.from(utf8Bytes, (byte) => String.fromCharCode(byte)).join('');
142
+ return btoa(binaryString);
143
+ }
144
+ catch (error) {
145
+ console.error('Base64 encoding failed:', error);
146
+ throw new Error('Failed to encode string to Base64');
147
+ }
148
+ };
149
+ const decodeBase64 = (base64) => {
150
+ try {
151
+ const binaryString = atob(base64);
152
+ const bytes = Uint8Array.from(binaryString, (char) => char.charCodeAt(0));
153
+ return new TextDecoder().decode(bytes);
154
+ }
155
+ catch (error) {
156
+ console.error('Base64 decoding failed:', error);
157
+ throw new Error('Failed to decode Base64 string');
158
+ }
159
+ };
160
+
161
+ const parseStringValue = (value) => {
162
+ if (value === 'true') {
163
+ return true;
164
+ }
165
+ if (value === 'false') {
166
+ return false;
167
+ }
168
+ if (!isNaN(Number(value))) {
169
+ return Number(value);
170
+ }
171
+ return value;
172
+ };
173
+ const cssRuleResolver = (rule, resourceValue) => {
174
+ const parsedResouceValue = parseStringValue(resourceValue);
175
+ const parsedConditionValue = parseStringValue(rule.if.value);
176
+ switch (rule.if.condition) {
177
+ case 'equals':
178
+ return parsedResouceValue === parsedConditionValue;
179
+ case 'notEquals':
180
+ return parsedResouceValue !== parsedConditionValue;
181
+ case 'greaterThan':
182
+ return parsedResouceValue > parsedConditionValue;
183
+ case 'greaterThanOrEqual':
184
+ return parsedResouceValue >= parsedConditionValue;
185
+ case 'lessThan':
186
+ return parsedResouceValue < parsedConditionValue;
187
+ case 'lessThanOrEqual':
188
+ return parsedResouceValue <= parsedConditionValue;
189
+ case 'contains':
190
+ if (Array.isArray(parsedResouceValue)) {
191
+ return parsedResouceValue.includes(parsedConditionValue);
192
+ }
193
+ else if (typeof parsedResouceValue === 'string' &&
194
+ typeof parsedConditionValue === 'string') {
195
+ return parsedResouceValue.includes(parsedConditionValue);
196
+ }
197
+ return false;
198
+ }
199
+ return false;
200
+ };
201
+ const evaluateCssRules = (value, rules) => {
202
+ if (!rules) {
203
+ return {};
204
+ }
205
+ return rules.reduce((acc, rule) => {
206
+ const result = cssRuleResolver(rule, value);
207
+ if (result) {
208
+ return { ...acc, ...rule.styles };
209
+ }
210
+ return acc;
211
+ }, {});
212
+ };
213
+
214
+ function getFieldValue(field, resource) {
215
+ if (resource) {
216
+ return getResourceValueByJsonPath(resource, field) ?? field.value;
217
+ }
218
+ return field.value;
219
+ }
220
+
221
+ const ICON_DESIGN_POSITIVE = 'Positive';
222
+ const ICON_DESIGN_NEGATIVE = 'Negative';
223
+ const ICON_NAME_POSITIVE = 'accept';
224
+ const ICON_NAME_NEGATIVE = 'decline';
225
+
226
+ class BooleanValue {
227
+ boolValue = input.required(...(ngDevMode ? [{ debugName: "boolValue" }] : /* istanbul ignore next */ []));
228
+ testId = input('boolean-value-icon', ...(ngDevMode ? [{ debugName: "testId" }] : /* istanbul ignore next */ []));
229
+ iconDesign = computed(() => {
230
+ return this.boolValue() ? ICON_DESIGN_POSITIVE : ICON_DESIGN_NEGATIVE;
231
+ }, ...(ngDevMode ? [{ debugName: "iconDesign" }] : /* istanbul ignore next */ []));
232
+ iconName = computed(() => {
233
+ return this.boolValue() ? ICON_NAME_POSITIVE : ICON_NAME_NEGATIVE;
234
+ }, ...(ngDevMode ? [{ debugName: "iconName" }] : /* istanbul ignore next */ []));
235
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: BooleanValue, deps: [], target: i0.ɵɵFactoryTarget.Component });
236
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.12", type: BooleanValue, isStandalone: true, selector: "mfp-boolean-value", inputs: { boolValue: { classPropertyName: "boolValue", publicName: "boolValue", isSignal: true, isRequired: true, transformFunction: null }, testId: { classPropertyName: "testId", publicName: "testId", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<ui5-icon [attr.test-id]=\"testId()\" [design]=\"iconDesign()\" [name]=\"iconName()\" />\n", styles: [""], dependencies: [{ kind: "component", type: Icon, selector: "ui5-icon, [ui5-icon]", inputs: ["design", "name", "accessibleName", "showTooltip", "mode"], outputs: ["ui5Click"], exportAs: ["ui5Icon"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
237
+ }
238
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: BooleanValue, decorators: [{
239
+ type: Component,
240
+ args: [{ selector: 'mfp-boolean-value', imports: [Icon], changeDetection: ChangeDetectionStrategy.OnPush, schemas: [CUSTOM_ELEMENTS_SCHEMA], template: "<ui5-icon [attr.test-id]=\"testId()\" [design]=\"iconDesign()\" [name]=\"iconName()\" />\n" }]
241
+ }], propDecorators: { boolValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "boolValue", required: true }] }], testId: [{ type: i0.Input, args: [{ isSignal: true, alias: "testId", required: false }] }] } });
242
+
243
+ class LinkValue {
244
+ urlValue = input.required(...(ngDevMode ? [{ debugName: "urlValue" }] : /* istanbul ignore next */ []));
245
+ testId = input('link-value-link', ...(ngDevMode ? [{ debugName: "testId" }] : /* istanbul ignore next */ []));
246
+ stopPropagation(event) {
247
+ event.stopPropagation();
248
+ }
249
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: LinkValue, deps: [], target: i0.ɵɵFactoryTarget.Component });
250
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.12", 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.test-id]=\"testId()\"\n [href]=\"urlValue()\"\n [tooltip]=\"urlValue()\"\n (click)=\"stopPropagation($event)\"\n>Link</ui5-link>\n", styles: [""], dependencies: [{ kind: "component", type: Link, selector: "ui5-link, [ui5-link]", inputs: ["disabled", "tooltip", "href", "target", "design", "interactiveAreaSize", "wrappingType", "accessibleName", "accessibleNameRef", "accessibleRole", "accessibilityAttributes", "accessibleDescription", "icon", "endIcon"], outputs: ["ui5Click"], exportAs: ["ui5Link"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
251
+ }
252
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: LinkValue, decorators: [{
253
+ type: Component,
254
+ args: [{ selector: 'mfp-link-value', imports: [Link], changeDetection: ChangeDetectionStrategy.OnPush, schemas: [CUSTOM_ELEMENTS_SCHEMA], template: "<ui5-link\n target=\"_blank\"\n [attr.test-id]=\"testId()\"\n [href]=\"urlValue()\"\n [tooltip]=\"urlValue()\"\n (click)=\"stopPropagation($event)\"\n>Link</ui5-link>\n" }]
255
+ }], propDecorators: { urlValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "urlValue", required: true }] }], testId: [{ type: i0.Input, args: [{ isSignal: true, alias: "testId", required: false }] }] } });
256
+
257
+ class SecretValue {
258
+ value = input.required(...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
259
+ isVisible = input(false, ...(ngDevMode ? [{ debugName: "isVisible" }] : /* istanbul ignore next */ []));
260
+ testId = input('secret-value', ...(ngDevMode ? [{ debugName: "testId" }] : /* istanbul ignore next */ []));
261
+ maskedValue = computed(() => '*'.repeat(this.value().length || 8), ...(ngDevMode ? [{ debugName: "maskedValue" }] : /* istanbul ignore next */ []));
262
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: SecretValue, deps: [], target: i0.ɵɵFactoryTarget.Component });
263
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: SecretValue, isStandalone: true, selector: "mfp-secret-value", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: true, transformFunction: null }, isVisible: { classPropertyName: "isVisible", publicName: "isVisible", isSignal: true, isRequired: false, transformFunction: null }, testId: { classPropertyName: "testId", publicName: "testId", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<span class=\"secret-value\" [attr.test-id]=\"testId()\" [class.masked]=\"!isVisible()\">\n @if (isVisible()) {\n {{ value() }}\n } @else {\n {{ maskedValue() }}\n }\n</span>\n", styles: [".secret-value{display:inline-flex;align-items:center;gap:.25rem}.masked{transform:translateY(.2em);display:flex}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
264
+ }
265
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: SecretValue, decorators: [{
266
+ type: Component,
267
+ args: [{ selector: 'mfp-secret-value', imports: [], changeDetection: ChangeDetectionStrategy.OnPush, schemas: [], template: "<span class=\"secret-value\" [attr.test-id]=\"testId()\" [class.masked]=\"!isVisible()\">\n @if (isVisible()) {\n {{ value() }}\n } @else {\n {{ maskedValue() }}\n }\n</span>\n", styles: [".secret-value{display:inline-flex;align-items:center;gap:.25rem}.masked{transform:translateY(.2em);display:flex}\n"] }]
268
+ }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: true }] }], isVisible: [{ type: i0.Input, args: [{ isSignal: true, alias: "isVisible", required: false }] }], testId: [{ type: i0.Input, args: [{ isSignal: true, alias: "testId", required: false }] }] } });
269
+
270
+ class ValueCell {
271
+ fieldDefinition = input.required(...(ngDevMode ? [{ debugName: "fieldDefinition" }] : /* istanbul ignore next */ []));
272
+ resource = input(...(ngDevMode ? [undefined, { debugName: "resource" }] : /* istanbul ignore next */ []));
273
+ buttonClick = output();
274
+ value = computed(() => getFieldValue(this.fieldDefinition(), this.resource()), ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
275
+ uiSettings = computed(() => this.fieldDefinition().uiSettings, ...(ngDevMode ? [{ debugName: "uiSettings" }] : /* istanbul ignore next */ []));
276
+ displayAs = computed(() => this.uiSettings()?.displayAs, ...(ngDevMode ? [{ debugName: "displayAs" }] : /* istanbul ignore next */ []));
277
+ withCopyButton = computed(() => this.uiSettings()?.withCopyButton, ...(ngDevMode ? [{ debugName: "withCopyButton" }] : /* istanbul ignore next */ []));
278
+ labelDisplay = computed(() => this.uiSettings()?.labelDisplay, ...(ngDevMode ? [{ debugName: "labelDisplay" }] : /* istanbul ignore next */ []));
279
+ cssCustomization = computed(() => this.uiSettings()?.cssCustomization, ...(ngDevMode ? [{ debugName: "cssCustomization" }] : /* istanbul ignore next */ []));
280
+ tooltipIcon = computed(() => this.uiSettings()?.tooltipIcon, ...(ngDevMode ? [{ debugName: "tooltipIcon" }] : /* istanbul ignore next */ []));
281
+ cssRules = computed(() => evaluateCssRules(this.value(), this.uiSettings()?.cssRules), ...(ngDevMode ? [{ debugName: "cssRules" }] : /* istanbul ignore next */ []));
282
+ cssStyles = computed(() => ({
283
+ ...this.cssCustomization(),
284
+ ...this.cssRules(),
285
+ }), ...(ngDevMode ? [{ debugName: "cssStyles" }] : /* istanbul ignore next */ []));
286
+ isBoolLike = computed(() => this.boolValue() !== undefined, ...(ngDevMode ? [{ debugName: "isBoolLike" }] : /* istanbul ignore next */ []));
287
+ isUrlValue = computed(() => this.checkValidUrl(this.stringValue()), ...(ngDevMode ? [{ debugName: "isUrlValue" }] : /* istanbul ignore next */ []));
288
+ testId = computed(() => `value-cell-${this.fieldDefinition().property}`, ...(ngDevMode ? [{ debugName: "testId" }] : /* istanbul ignore next */ []));
289
+ boolValue = computed(() => this.normalizeBoolean(this.value()), ...(ngDevMode ? [{ debugName: "boolValue" }] : /* istanbul ignore next */ []));
290
+ stringValue = computed(() => this.normalizeString(this.value()), ...(ngDevMode ? [{ debugName: "stringValue" }] : /* istanbul ignore next */ []));
291
+ isVisible = signal(false, ...(ngDevMode ? [{ debugName: "isVisible" }] : /* istanbul ignore next */ []));
292
+ toggleVisibility(e) {
293
+ e.stopPropagation();
294
+ this.isVisible.set(!this.isVisible());
295
+ }
296
+ normalizeBoolean(value) {
297
+ const normalizedValue = value?.toString()?.toLowerCase();
298
+ if (normalizedValue === 'true') {
299
+ return true;
300
+ }
301
+ if (normalizedValue === 'false') {
302
+ return false;
303
+ }
304
+ return undefined;
305
+ }
306
+ normalizeString(value) {
307
+ if (typeof value !== 'string' || !value.trim()) {
308
+ return undefined;
309
+ }
310
+ return value;
311
+ }
312
+ checkValidUrl(value) {
313
+ if (!value) {
314
+ return false;
315
+ }
316
+ try {
317
+ new URL(value);
318
+ return true;
319
+ }
320
+ catch {
321
+ return false;
322
+ }
323
+ }
324
+ copyValue(event) {
325
+ event.stopPropagation();
326
+ navigator.clipboard.writeText(this.value() || '');
327
+ }
328
+ buttonClicked(event) {
329
+ event.stopPropagation();
330
+ this.buttonClick.emit({
331
+ event,
332
+ field: this.fieldDefinition(),
333
+ resource: this.resource(),
334
+ });
335
+ }
336
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: ValueCell, deps: [], target: i0.ɵɵFactoryTarget.Component });
337
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: ValueCell, isStandalone: true, selector: "mfp-value-cell", inputs: { fieldDefinition: { classPropertyName: "fieldDefinition", publicName: "fieldDefinition", isSignal: true, isRequired: true, transformFunction: null }, resource: { classPropertyName: "resource", publicName: "resource", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { buttonClick: "buttonClick" }, ngImport: i0, template: "@let displayType = displayAs();\n<span [attr.test-id]=\"testId()\" [class.label-value]=\"labelDisplay()\" [style]=\"cssStyles()\">\n @if (displayType === 'secret') {\n <span class=\"secret-value-container\">\n <mfp-secret-value [isVisible]=\"isVisible()\" [testId]=\"testId() + '-secret'\" [value]=\"value()\" />\n <ui5-icon\n class=\"toggle-icon\"\n [attr.test-id]=\"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 [boolValue]=\"boolValue()!\" [testId]=\"testId() + '-boolean'\" />\n } @else if (displayType === 'link' && isUrlValue()) {\n <mfp-link-value [testId]=\"testId() + '-link'\" [urlValue]=\"stringValue()!\" />\n } @else if (displayType === 'tooltip') {\n <ui5-icon\n [accessibleName]=\"stringValue()!\"\n [attr.test-id]=\"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.test-id]=\"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 [design]=\"buttonSettings?.design\"\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 {\n {{ value() }}\n }\n</span>\n\n@if (withCopyButton()) {\n <ui5-icon\n class=\"toggle-icon\"\n name=\"copy\"\n [attr.test-id]=\"testId() + '-copy'\"\n (click)=\"copyValue($event)\"\n />\n}\n", styles: [":host{display:inline-flex;align-items:center}.label-value{background-color:#01b4ff;color:#fff;border-radius:1.4rem;padding-inline:.4375rem;letter-spacing:-.00875rem;display:inline-block;border:0;font-size:.875rem;line-height:1.4rem;margin:3px}.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: Icon, selector: "ui5-icon, [ui5-icon]", inputs: ["design", "name", "accessibleName", "showTooltip", "mode"], outputs: ["ui5Click"], exportAs: ["ui5Icon"] }, { kind: "component", type: BooleanValue, selector: "mfp-boolean-value", inputs: ["boolValue", "testId"] }, { kind: "component", type: LinkValue, selector: "mfp-link-value", inputs: ["urlValue", "testId"] }, { kind: "component", type: SecretValue, selector: "mfp-secret-value", inputs: ["value", "isVisible", "testId"] }, { kind: "component", type: Button, selector: "ui5-button, [ui5-button]", inputs: ["design", "disabled", "icon", "endIcon", "submits", "tooltip", "accessibleName", "accessibleNameRef", "accessibilityAttributes", "accessibleDescription", "type", "accessibleRole", "loading", "loadingDelay"], outputs: ["ui5Click"], exportAs: ["ui5Button"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.ShadowDom });
338
+ }
339
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: ValueCell, decorators: [{
340
+ type: Component,
341
+ args: [{ selector: 'mfp-value-cell', imports: [Icon, BooleanValue, LinkValue, SecretValue, Button], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.ShadowDom, schemas: [CUSTOM_ELEMENTS_SCHEMA], template: "@let displayType = displayAs();\n<span [attr.test-id]=\"testId()\" [class.label-value]=\"labelDisplay()\" [style]=\"cssStyles()\">\n @if (displayType === 'secret') {\n <span class=\"secret-value-container\">\n <mfp-secret-value [isVisible]=\"isVisible()\" [testId]=\"testId() + '-secret'\" [value]=\"value()\" />\n <ui5-icon\n class=\"toggle-icon\"\n [attr.test-id]=\"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 [boolValue]=\"boolValue()!\" [testId]=\"testId() + '-boolean'\" />\n } @else if (displayType === 'link' && isUrlValue()) {\n <mfp-link-value [testId]=\"testId() + '-link'\" [urlValue]=\"stringValue()!\" />\n } @else if (displayType === 'tooltip') {\n <ui5-icon\n [accessibleName]=\"stringValue()!\"\n [attr.test-id]=\"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.test-id]=\"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 [design]=\"buttonSettings?.design\"\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 {\n {{ value() }}\n }\n</span>\n\n@if (withCopyButton()) {\n <ui5-icon\n class=\"toggle-icon\"\n name=\"copy\"\n [attr.test-id]=\"testId() + '-copy'\"\n (click)=\"copyValue($event)\"\n />\n}\n", styles: [":host{display:inline-flex;align-items:center}.label-value{background-color:#01b4ff;color:#fff;border-radius:1.4rem;padding-inline:.4375rem;letter-spacing:-.00875rem;display:inline-block;border:0;font-size:.875rem;line-height:1.4rem;margin:3px}.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"] }]
342
+ }], propDecorators: { fieldDefinition: [{ type: i0.Input, args: [{ isSignal: true, alias: "fieldDefinition", required: true }] }], resource: [{ type: i0.Input, args: [{ isSignal: true, alias: "resource", required: false }] }], buttonClick: [{ type: i0.Output, args: ["buttonClick"] }] } });
343
+
344
+ class DeclarativeTable {
345
+ columns = input.required(...(ngDevMode ? [{ debugName: "columns" }] : /* istanbul ignore next */ []));
346
+ resources = input.required(...(ngDevMode ? [{ debugName: "resources" }] : /* istanbul ignore next */ []));
347
+ trackByPath = input('id', ...(ngDevMode ? [{ debugName: "trackByPath" }] : /* istanbul ignore next */ []));
348
+ totalItemsCount = input(...(ngDevMode ? [undefined, { debugName: "totalItemsCount" }] : /* istanbul ignore next */ []));
349
+ paginationLimit = input(5, ...(ngDevMode ? [{ debugName: "paginationLimit" }] : /* istanbul ignore next */ []));
350
+ hasMore = input(false, ...(ngDevMode ? [{ debugName: "hasMore" }] : /* istanbul ignore next */ []));
351
+ buttonClick = output();
352
+ tableRowClicked = output();
353
+ loadMoreResources = output();
354
+ paginationLimitChanged = output();
355
+ columnTrackBy = (column, index) => column.property ?? column.value ?? index;
356
+ rowTrackBy = (_index, item) => getResourceValueByJsonPath(item, { property: this.trackByPath() }) ?? _index;
357
+ viewColumns = computed(() => processGroupFields(this.columns()), ...(ngDevMode ? [{ debugName: "viewColumns" }] : /* istanbul ignore next */ []));
358
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: DeclarativeTable, deps: [], target: i0.ɵɵFactoryTarget.Component });
359
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: DeclarativeTable, isStandalone: true, selector: "mfp-declarative-table", inputs: { columns: { classPropertyName: "columns", publicName: "columns", isSignal: true, isRequired: true, transformFunction: null }, resources: { classPropertyName: "resources", publicName: "resources", isSignal: true, isRequired: true, transformFunction: null }, trackByPath: { classPropertyName: "trackByPath", publicName: "trackByPath", isSignal: true, isRequired: false, transformFunction: null }, totalItemsCount: { classPropertyName: "totalItemsCount", publicName: "totalItemsCount", isSignal: true, isRequired: false, transformFunction: null }, paginationLimit: { classPropertyName: "paginationLimit", publicName: "paginationLimit", isSignal: true, isRequired: false, transformFunction: null }, hasMore: { classPropertyName: "hasMore", publicName: "hasMore", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { buttonClick: "buttonClick", tableRowClicked: "tableRowClicked", loadMoreResources: "loadMoreResources", paginationLimitChanged: "paginationLimitChanged" }, ngImport: i0, template: "<ui5-table test-id=\"generic-table\">\n <ui5-table-header-row slot=\"headerRow\">\n @for (column of viewColumns(); track columnTrackBy(column, $index)) {\n @if (column.group) {\n <ui5-table-header-cell\n [attr.test-id]=\"'generic-table-header-' + column.property\"\n >{{ column.group.label ?? column.group.name }}</ui5-table-header-cell\n >\n } @else {\n <ui5-table-header-cell\n [attr.test-id]=\"'generic-table-header-' + column.property\"\n [width]=\"column.uiSettings?.columnWidth ?? 'auto'\"\n >{{ column.label }}</ui5-table-header-cell\n >\n }\n }\n </ui5-table-header-row>\n\n @for (item of resources(); let i = $index; track rowTrackBy($index, item)) {\n <ui5-table-row\n [attr.test-id]=\"'generic-table-row-' + i\"\n [class.disabled]=\"!(item.isAvailable ?? true)\"\n [interactive]=\"item.isAvailable ?? true\"\n (click)=\"tableRowClicked.emit(item)\"\n >\n @for (column of viewColumns(); track columnTrackBy(column, $index)) {\n @if (column.group) {\n <ui5-table-cell\n [attr.test-id]=\"'generic-table-cell-' + i + '-' + column.property\"\n [class.multiline]=\"column.group.multiline ?? true\"\n >\n @for (\n field of column.group.fields;\n let last = $last;\n track columnTrackBy(field, $index)\n ) {\n <span\n class=\"group-value\"\n [attr.test-id]=\"\n 'generic-table-cell-' +\n i +\n '-' +\n column.property +\n '-' +\n field.property\n \"\n >\n @if (field.label) {\n <span>{{ field.label }}: </span>\n }\n <mfp-value-cell\n [fieldDefinition]=\"field\"\n [resource]=\"item\"\n (buttonClick)=\"buttonClick.emit($event)\"\n />\n </span>\n @if (!last && column.group.delimiter) {\n <span>{{ column.group.delimiter }}</span>\n }\n }\n </ui5-table-cell>\n } @else {\n <ui5-table-cell\n [attr.test-id]=\"'generic-table-cell-' + i + '-' + column.property\"\n >\n <mfp-value-cell\n [fieldDefinition]=\"column\"\n [resource]=\"item\"\n (buttonClick)=\"buttonClick.emit($event)\"\n />\n </ui5-table-cell>\n }\n }\n </ui5-table-row>\n } @empty {\n <ui5-illustrated-message\n name=\"NoData\"\n slot=\"noData\"\n test-id=\"generic-table-view-nodata\"\n >\n <span slot=\"title\">No Resources</span>\n <span slot=\"subtitle\">There are currently no items to show.</span>\n </ui5-illustrated-message>\n }\n\n @if (hasMore()) {\n <ui5-table-growing\n id=\"growing\"\n mode=\"Button\"\n slot=\"features\"\n text=\"Load more\"\n (ui5LoadMore)=\"loadMoreResources.emit()\"\n />\n }\n</ui5-table>\n\n<!-- Pagination Control -->\n<div\n class=\"pagination-footer\"\n style=\"\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 0rem 1rem;\n min-height: 3rem;\n border-top: 1px solid var(--sapList_BorderColor);\n \"\n>\n <div style=\"display: flex; align-items: center; gap: 1.5rem\">\n <div style=\"display: flex; align-items: center; gap: 0.5rem\">\n <span\n style=\"color: var(--sapTextColor); font-size: var(--sapFontSmallSize)\"\n >Items per load:</span\n >\n <ui5-select\n style=\"width: 5rem\"\n (change)=\"paginationLimitChanged.emit(+$any($event)?.target?.value)\"\n [value]=\"paginationLimit().toString()\"\n >\n <ui5-option value=\"5\">5</ui5-option>\n <ui5-option value=\"10\">10</ui5-option>\n <ui5-option value=\"50\">50</ui5-option>\n <ui5-option value=\"100\">100</ui5-option>\n </ui5-select>\n </div>\n\n <span\n style=\"color: var(--sapTextColor); font-size: var(--sapFontSmallSize)\"\n >\n Items loaded:\n <b>{{\n totalItemsCount() !== undefined\n ? resources().length + ' / ' + totalItemsCount()\n : resources().length\n }}</b>\n </span>\n </div>\n</div>\n", styles: [".multiline{display:block}.disabled{filter:brightness(.92);pointer-events:none}.disabled ui5-table-cell:not(:first-child){color:var(--sapContent_DisabledTextColor, #6a6d70)}.disabled ui5-table-cell:not(:first-child) ui5-icon{color:var(--sapContent_DisabledTextColor, #6a6d70)}.group-value{display:flex;align-items:center}\n"], dependencies: [{ kind: "component", type: IllustratedMessage, selector: "ui5-illustrated-message, [ui5-illustrated-message]", inputs: ["name", "design", "subtitleText", "titleText", "accessibleNameRef", "decorative"], exportAs: ["ui5IllustratedMessage"] }, { kind: "component", type: Table, selector: "ui5-table, [ui5-table]", inputs: ["accessibleName", "accessibleNameRef", "noDataText", "overflowMode", "loading", "loadingDelay", "rowActionCount", "alternateRowColors"], outputs: ["ui5RowClick", "ui5MoveOver", "ui5Move", "ui5RowActionClick"], exportAs: ["ui5Table"] }, { kind: "component", type: TableCell, selector: "ui5-table-cell, [ui5-table-cell]", inputs: ["horizontalAlign"], exportAs: ["ui5TableCell"] }, { kind: "component", type: TableHeaderCell, selector: "ui5-table-header-cell, [ui5-table-header-cell]", inputs: ["width", "minWidth", "importance", "popinText", "sortIndicator", "popinHidden", "horizontalAlign"], exportAs: ["ui5TableHeaderCell"] }, { kind: "component", type: TableHeaderRow, selector: "ui5-table-header-row, [ui5-table-header-row]", inputs: ["sticky"], exportAs: ["ui5TableHeaderRow"] }, { kind: "component", type: TableRow, selector: "ui5-table-row, [ui5-table-row]", inputs: ["rowKey", "position", "interactive", "navigated", "movable"], exportAs: ["ui5TableRow"] }, { kind: "component", type: ValueCell, selector: "mfp-value-cell", inputs: ["fieldDefinition", "resource"], outputs: ["buttonClick"] }, { kind: "component", type: Select, selector: "ui5-select, [ui5-select]", inputs: ["disabled", "name", "valueState", "required", "readonly", "accessibleName", "accessibleNameRef", "accessibleDescription", "accessibleDescriptionRef", "tooltip", "textSeparator", "value"], outputs: ["ui5Change", "ui5LiveChange", "ui5Open", "ui5Close"], exportAs: ["ui5Select"] }, { kind: "component", type: Option, selector: "ui5-option, [ui5-option]", inputs: ["value", "icon", "additionalText", "tooltip", "selected"], exportAs: ["ui5Option"] }, { kind: "component", type: TableGrowing, selector: "ui5-table-growing, [ui5-table-growing]", inputs: ["mode", "text", "subtext"], outputs: ["ui5LoadMore"], exportAs: ["ui5TableGrowing"] }], encapsulation: i0.ViewEncapsulation.ShadowDom });
360
+ }
361
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: DeclarativeTable, decorators: [{
362
+ type: Component,
363
+ args: [{ selector: 'mfp-declarative-table', imports: [
364
+ IllustratedMessage,
365
+ Table,
366
+ TableCell,
367
+ TableHeaderCell,
368
+ TableHeaderRow,
369
+ TableRow,
370
+ ValueCell,
371
+ Select,
372
+ Option,
373
+ TableGrowing,
374
+ ], encapsulation: ViewEncapsulation.ShadowDom, template: "<ui5-table test-id=\"generic-table\">\n <ui5-table-header-row slot=\"headerRow\">\n @for (column of viewColumns(); track columnTrackBy(column, $index)) {\n @if (column.group) {\n <ui5-table-header-cell\n [attr.test-id]=\"'generic-table-header-' + column.property\"\n >{{ column.group.label ?? column.group.name }}</ui5-table-header-cell\n >\n } @else {\n <ui5-table-header-cell\n [attr.test-id]=\"'generic-table-header-' + column.property\"\n [width]=\"column.uiSettings?.columnWidth ?? 'auto'\"\n >{{ column.label }}</ui5-table-header-cell\n >\n }\n }\n </ui5-table-header-row>\n\n @for (item of resources(); let i = $index; track rowTrackBy($index, item)) {\n <ui5-table-row\n [attr.test-id]=\"'generic-table-row-' + i\"\n [class.disabled]=\"!(item.isAvailable ?? true)\"\n [interactive]=\"item.isAvailable ?? true\"\n (click)=\"tableRowClicked.emit(item)\"\n >\n @for (column of viewColumns(); track columnTrackBy(column, $index)) {\n @if (column.group) {\n <ui5-table-cell\n [attr.test-id]=\"'generic-table-cell-' + i + '-' + column.property\"\n [class.multiline]=\"column.group.multiline ?? true\"\n >\n @for (\n field of column.group.fields;\n let last = $last;\n track columnTrackBy(field, $index)\n ) {\n <span\n class=\"group-value\"\n [attr.test-id]=\"\n 'generic-table-cell-' +\n i +\n '-' +\n column.property +\n '-' +\n field.property\n \"\n >\n @if (field.label) {\n <span>{{ field.label }}: </span>\n }\n <mfp-value-cell\n [fieldDefinition]=\"field\"\n [resource]=\"item\"\n (buttonClick)=\"buttonClick.emit($event)\"\n />\n </span>\n @if (!last && column.group.delimiter) {\n <span>{{ column.group.delimiter }}</span>\n }\n }\n </ui5-table-cell>\n } @else {\n <ui5-table-cell\n [attr.test-id]=\"'generic-table-cell-' + i + '-' + column.property\"\n >\n <mfp-value-cell\n [fieldDefinition]=\"column\"\n [resource]=\"item\"\n (buttonClick)=\"buttonClick.emit($event)\"\n />\n </ui5-table-cell>\n }\n }\n </ui5-table-row>\n } @empty {\n <ui5-illustrated-message\n name=\"NoData\"\n slot=\"noData\"\n test-id=\"generic-table-view-nodata\"\n >\n <span slot=\"title\">No Resources</span>\n <span slot=\"subtitle\">There are currently no items to show.</span>\n </ui5-illustrated-message>\n }\n\n @if (hasMore()) {\n <ui5-table-growing\n id=\"growing\"\n mode=\"Button\"\n slot=\"features\"\n text=\"Load more\"\n (ui5LoadMore)=\"loadMoreResources.emit()\"\n />\n }\n</ui5-table>\n\n<!-- Pagination Control -->\n<div\n class=\"pagination-footer\"\n style=\"\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 0rem 1rem;\n min-height: 3rem;\n border-top: 1px solid var(--sapList_BorderColor);\n \"\n>\n <div style=\"display: flex; align-items: center; gap: 1.5rem\">\n <div style=\"display: flex; align-items: center; gap: 0.5rem\">\n <span\n style=\"color: var(--sapTextColor); font-size: var(--sapFontSmallSize)\"\n >Items per load:</span\n >\n <ui5-select\n style=\"width: 5rem\"\n (change)=\"paginationLimitChanged.emit(+$any($event)?.target?.value)\"\n [value]=\"paginationLimit().toString()\"\n >\n <ui5-option value=\"5\">5</ui5-option>\n <ui5-option value=\"10\">10</ui5-option>\n <ui5-option value=\"50\">50</ui5-option>\n <ui5-option value=\"100\">100</ui5-option>\n </ui5-select>\n </div>\n\n <span\n style=\"color: var(--sapTextColor); font-size: var(--sapFontSmallSize)\"\n >\n Items loaded:\n <b>{{\n totalItemsCount() !== undefined\n ? resources().length + ' / ' + totalItemsCount()\n : resources().length\n }}</b>\n </span>\n </div>\n</div>\n", styles: [".multiline{display:block}.disabled{filter:brightness(.92);pointer-events:none}.disabled ui5-table-cell:not(:first-child){color:var(--sapContent_DisabledTextColor, #6a6d70)}.disabled ui5-table-cell:not(:first-child) ui5-icon{color:var(--sapContent_DisabledTextColor, #6a6d70)}.group-value{display:flex;align-items:center}\n"] }]
375
+ }], propDecorators: { columns: [{ type: i0.Input, args: [{ isSignal: true, alias: "columns", required: true }] }], resources: [{ type: i0.Input, args: [{ isSignal: true, alias: "resources", required: true }] }], trackByPath: [{ type: i0.Input, args: [{ isSignal: true, alias: "trackByPath", required: false }] }], totalItemsCount: [{ type: i0.Input, args: [{ isSignal: true, alias: "totalItemsCount", required: false }] }], paginationLimit: [{ type: i0.Input, args: [{ isSignal: true, alias: "paginationLimit", required: false }] }], hasMore: [{ type: i0.Input, args: [{ isSignal: true, alias: "hasMore", required: false }] }], buttonClick: [{ type: i0.Output, args: ["buttonClick"] }], tableRowClicked: [{ type: i0.Output, args: ["tableRowClicked"] }], loadMoreResources: [{ type: i0.Output, args: ["loadMoreResources"] }], paginationLimitChanged: [{ type: i0.Output, args: ["paginationLimitChanged"] }] } });
376
+
377
+ function setPropertyByPath(object, path, value) {
378
+ const segments = path.split('.').filter(Boolean);
379
+ if (segments.length === 0) {
380
+ return object;
381
+ }
382
+ let current = object;
383
+ for (let i = 0; i < segments.length; i += 1) {
384
+ const key = segments[i];
385
+ if (i === segments.length - 1) {
386
+ current[key] = value;
387
+ break;
388
+ }
389
+ const existing = current[key];
390
+ if (existing === undefined || existing === null || typeof existing !== 'object') {
391
+ current[key] = {};
392
+ }
393
+ current = current[key];
394
+ }
395
+ return object;
396
+ }
397
+
398
+ class DeclarativeForm {
399
+ fields = input.required(...(ngDevMode ? [{ debugName: "fields" }] : /* istanbul ignore next */ []));
400
+ initialValues = input({}, ...(ngDevMode ? [{ debugName: "initialValues" }] : /* istanbul ignore next */ []));
401
+ fieldErrors = input({}, ...(ngDevMode ? [{ debugName: "fieldErrors" }] : /* istanbul ignore next */ []));
402
+ fieldChange = output();
403
+ formSubmit = output();
404
+ form;
405
+ fb = inject(FormBuilder);
406
+ constructor() {
407
+ this.form = this.fb.group({});
408
+ effect(() => {
409
+ this.rebuildControls(this.fields());
410
+ });
411
+ effect(() => {
412
+ this.setInitialValues(this.initialValues());
413
+ });
414
+ effect(() => {
415
+ const fields = this.fields();
416
+ this.initialValues();
417
+ for (const field of fields) {
418
+ if (field.validation) {
419
+ this.fieldChange.emit({
420
+ fieldProperty: field.name,
421
+ value: this.form.controls[field.name]?.value ?? '',
422
+ });
423
+ }
424
+ }
425
+ });
426
+ }
427
+ setFormControlValue($event, field) {
428
+ const target = $event.target;
429
+ const control = this.form.controls[field.name];
430
+ control.setValue(target.value);
431
+ control.markAsTouched();
432
+ control.markAsDirty();
433
+ if (field.validation === 'onChange') {
434
+ this.fieldChange.emit({
435
+ fieldProperty: field.name,
436
+ value: control.value,
437
+ });
438
+ }
439
+ }
440
+ getError(name) {
441
+ const control = this.form.controls[name];
442
+ const error = this.fieldErrors()[name];
443
+ return error && (control.dirty || control.touched) ? error : null;
444
+ }
445
+ getValueState(name) {
446
+ return this.getError(name) ? 'Negative' : 'None';
447
+ }
448
+ onFieldBlur(field) {
449
+ this.form.controls[field.name]?.markAsTouched();
450
+ if (field.validation === 'onBlur') {
451
+ this.fieldChange.emit({
452
+ fieldProperty: field.name,
453
+ value: this.form.controls[field.name]?.value,
454
+ });
455
+ }
456
+ }
457
+ submit() {
458
+ this.formSubmit.emit(this.buildOutputValue());
459
+ }
460
+ clear() {
461
+ this.form.reset();
462
+ }
463
+ rebuildControls(fields) {
464
+ const existingControls = this.form.controls;
465
+ const nextFieldNames = new Set(fields.map((field) => field.name));
466
+ for (const field of fields) {
467
+ const existingControl = existingControls[field.name];
468
+ if (existingControl) {
469
+ existingControl.clearValidators();
470
+ existingControl.updateValueAndValidity({ emitEvent: false });
471
+ continue;
472
+ }
473
+ this.form.addControl(field.name, new FormControl(''));
474
+ }
475
+ for (const controlName of Object.keys(existingControls)) {
476
+ if (!nextFieldNames.has(controlName)) {
477
+ this.form.removeControl(controlName);
478
+ }
479
+ }
480
+ this.form.updateValueAndValidity({ emitEvent: false });
481
+ }
482
+ setInitialValues(initialValues) {
483
+ if (!initialValues) {
484
+ return;
485
+ }
486
+ this.form.patchValue(initialValues, { emitEvent: false });
487
+ this.form.markAsPristine({ emitEvent: false });
488
+ this.form.updateValueAndValidity({ emitEvent: false });
489
+ }
490
+ buildOutputValue() {
491
+ const result = {};
492
+ for (const key of Object.keys(this.form.controls)) {
493
+ setPropertyByPath(result, key, this.form.controls[key].value);
494
+ }
495
+ return result;
496
+ }
497
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: DeclarativeForm, deps: [], target: i0.ɵɵFactoryTarget.Component });
498
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: DeclarativeForm, isStandalone: true, selector: "mfp-declarative-form", inputs: { fields: { classPropertyName: "fields", publicName: "fields", isSignal: true, isRequired: true, 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" }, ngImport: i0, template: "<section test-id=\"generic-form\" class=\"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.test-id]=\"'generic-form-field-container-' + field.name\"\n >\n <ui5-label\n [attr.test-id]=\"'generic-form-field-label-' + field.name\"\n show-colon\n [required]=\"field.required\"\n >{{ field.label }}</ui5-label\n >\n @if (field.values?.length) {\n <ui5-select\n class=\"input\"\n [attr.test-id]=\"'generic-form-field-' + field.name\"\n [value]=\"control.value\"\n (input)=\"setFormControlValue($event, field)\"\n (change)=\"setFormControlValue($event, field)\"\n (blur)=\"onFieldBlur(field)\"\n [required]=\"field.required\"\n [valueState]=\"getValueState(field.name)\"\n [disabled]=\"field.disabled\"\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.test-id]=\"\n 'generic-form-field-' +\n field.name +\n '-option-' +\n (value || 'empty')\n \"\n [value]=\"value\"\n [selected]=\"value === control.value\"\n >{{ value }}</ui5-option\n >\n }\n </ui5-select>\n } @else {\n <ui5-input\n class=\"input\"\n [attr.test-id]=\"'generic-form-field-' + field.name\"\n [value]=\"control.value\"\n (blur)=\"onFieldBlur(field)\"\n (change)=\"setFormControlValue($event, field)\"\n (input)=\"setFormControlValue($event, field)\"\n [required]=\"field.required\"\n [valueState]=\"getValueState(field.name)\"\n [disabled]=\"field.disabled\"\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%}\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: ["disabled", "placeholder", "readonly", "required", "noTypeahead", "type", "value", "valueState", "name", "showSuggestions", "maxlength", "accessibleName", "accessibleNameRef", "accessibleDescription", "accessibleDescriptionRef", "showClearIcon", "open", "filter"], outputs: ["ui5Change", "ui5Input", "ui5Select", "ui5SelectionChange", "ui5Open", "ui5Close"], exportAs: ["ui5Input"] }, { kind: "component", type: Label, selector: "ui5-label, [ui5-label]", inputs: ["for", "showColon", "required", "wrappingType"], exportAs: ["ui5Label"] }, { kind: "component", type: Select, selector: "ui5-select, [ui5-select]", inputs: ["disabled", "name", "valueState", "required", "readonly", "accessibleName", "accessibleNameRef", "accessibleDescription", "accessibleDescriptionRef", "tooltip", "textSeparator", "value"], outputs: ["ui5Change", "ui5LiveChange", "ui5Open", "ui5Close"], exportAs: ["ui5Select"] }, { kind: "component", type: Option, selector: "ui5-option, [ui5-option]", inputs: ["value", "icon", "additionalText", "tooltip", "selected"], exportAs: ["ui5Option"] }], encapsulation: i0.ViewEncapsulation.ShadowDom });
499
+ }
500
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: DeclarativeForm, decorators: [{
501
+ type: Component,
502
+ args: [{ selector: 'mfp-declarative-form', imports: [ReactiveFormsModule, Input, Label, Select, Option], encapsulation: ViewEncapsulation.ShadowDom, template: "<section test-id=\"generic-form\" class=\"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.test-id]=\"'generic-form-field-container-' + field.name\"\n >\n <ui5-label\n [attr.test-id]=\"'generic-form-field-label-' + field.name\"\n show-colon\n [required]=\"field.required\"\n >{{ field.label }}</ui5-label\n >\n @if (field.values?.length) {\n <ui5-select\n class=\"input\"\n [attr.test-id]=\"'generic-form-field-' + field.name\"\n [value]=\"control.value\"\n (input)=\"setFormControlValue($event, field)\"\n (change)=\"setFormControlValue($event, field)\"\n (blur)=\"onFieldBlur(field)\"\n [required]=\"field.required\"\n [valueState]=\"getValueState(field.name)\"\n [disabled]=\"field.disabled\"\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.test-id]=\"\n 'generic-form-field-' +\n field.name +\n '-option-' +\n (value || 'empty')\n \"\n [value]=\"value\"\n [selected]=\"value === control.value\"\n >{{ value }}</ui5-option\n >\n }\n </ui5-select>\n } @else {\n <ui5-input\n class=\"input\"\n [attr.test-id]=\"'generic-form-field-' + field.name\"\n [value]=\"control.value\"\n (blur)=\"onFieldBlur(field)\"\n (change)=\"setFormControlValue($event, field)\"\n (input)=\"setFormControlValue($event, field)\"\n [required]=\"field.required\"\n [valueState]=\"getValueState(field.name)\"\n [disabled]=\"field.disabled\"\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%}\n"] }]
503
+ }], ctorParameters: () => [], propDecorators: { fields: [{ type: i0.Input, args: [{ isSignal: true, alias: "fields", required: true }] }], 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"] }] } });
504
+
505
+ class DeclarativeTableCard {
506
+ resources = input.required(...(ngDevMode ? [{ debugName: "resources" }] : /* istanbul ignore next */ []));
507
+ config = input.required(...(ngDevMode ? [{ debugName: "config" }] : /* istanbul ignore next */ []));
508
+ createFormState = input({}, ...(ngDevMode ? [{ debugName: "createFormState" }] : /* istanbul ignore next */ []));
509
+ editFormState = input({}, ...(ngDevMode ? [{ debugName: "editFormState" }] : /* istanbul ignore next */ []));
510
+ actionButtonClick = output();
511
+ tableRowClicked = output();
512
+ loadMoreResources = output();
513
+ paginationLimitChanged = output();
514
+ searchChanged = output();
515
+ createFieldChange = output();
516
+ editFieldChange = output();
517
+ createSubmit = output();
518
+ editSubmit = output();
519
+ deleteSubmit = output();
520
+ searchState = signal('collapsed', ...(ngDevMode ? [{ debugName: "searchState" }] : /* istanbul ignore next */ []));
521
+ searchExpanded = computed(() => this.searchState() !== 'collapsed', ...(ngDevMode ? [{ debugName: "searchExpanded" }] : /* istanbul ignore next */ []));
522
+ searchCollapsing = computed(() => this.searchState() === 'collapsing', ...(ngDevMode ? [{ debugName: "searchCollapsing" }] : /* istanbul ignore next */ []));
523
+ searchControl = new FormControl('');
524
+ searchInputRef = viewChild('searchInput', ...(ngDevMode ? [{ debugName: "searchInputRef" }] : /* istanbul ignore next */ []));
525
+ createDialogOpen = signal(false, ...(ngDevMode ? [{ debugName: "createDialogOpen" }] : /* istanbul ignore next */ []));
526
+ editDialogOpen = signal(false, ...(ngDevMode ? [{ debugName: "editDialogOpen" }] : /* istanbul ignore next */ []));
527
+ deleteDialogOpen = signal(false, ...(ngDevMode ? [{ debugName: "deleteDialogOpen" }] : /* istanbul ignore next */ []));
528
+ pendingResource = signal(null, ...(ngDevMode ? [{ debugName: "pendingResource" }] : /* istanbul ignore next */ []));
529
+ tableConfig = computed(() => this.config().tableConfig, ...(ngDevMode ? [{ debugName: "tableConfig" }] : /* istanbul ignore next */ []));
530
+ header = computed(() => this.config().header, ...(ngDevMode ? [{ debugName: "header" }] : /* istanbul ignore next */ []));
531
+ headerTooltip = computed(() => this.config().headerTooltip, ...(ngDevMode ? [{ debugName: "headerTooltip" }] : /* istanbul ignore next */ []));
532
+ createFormConfig = computed(() => this.config().createResourceFormConfig, ...(ngDevMode ? [{ debugName: "createFormConfig" }] : /* istanbul ignore next */ []));
533
+ editFormConfig = computed(() => this.config().editResourceFormConfig, ...(ngDevMode ? [{ debugName: "editFormConfig" }] : /* istanbul ignore next */ []));
534
+ deleteConfirmationConfig = computed(() => this.config().deleteResourceConfirmationConfig, ...(ngDevMode ? [{ debugName: "deleteConfirmationConfig" }] : /* istanbul ignore next */ []));
535
+ createButtonConfig = computed(() => this.config().buttonSettings?.createButton, ...(ngDevMode ? [{ debugName: "createButtonConfig" }] : /* istanbul ignore next */ []));
536
+ searchButtonConfig = computed(() => this.config().buttonSettings?.searchButton, ...(ngDevMode ? [{ debugName: "searchButtonConfig" }] : /* istanbul ignore next */ []));
537
+ effectiveColumns = computed(() => this.addActionsColumn(), ...(ngDevMode ? [{ debugName: "effectiveColumns" }] : /* istanbul ignore next */ []));
538
+ editInitialValue = computed(() => {
539
+ const pendingResource = this.pendingResource();
540
+ const editConfig = this.editFormConfig();
541
+ if (!pendingResource || !editConfig) {
542
+ return {};
543
+ }
544
+ return this.buildInitialValues(editConfig.fields, pendingResource);
545
+ }, ...(ngDevMode ? [{ debugName: "editInitialValue" }] : /* istanbul ignore next */ []));
546
+ injector = inject(Injector);
547
+ constructor() {
548
+ this.searchControl.valueChanges
549
+ .pipe(debounceTime(300), takeUntilDestroyed())
550
+ .subscribe((value) => {
551
+ this.searchChanged.emit(value ?? '');
552
+ });
553
+ }
554
+ toggleSearch() {
555
+ if (this.searchState() === 'expanded') {
556
+ this.collapseSearch();
557
+ }
558
+ else if (this.searchState() === 'collapsed') {
559
+ this.searchState.set('expanded');
560
+ afterNextRender(() => {
561
+ this.searchInputRef()?.elementRef.nativeElement.focus();
562
+ }, { injector: this.injector });
563
+ }
564
+ }
565
+ onSearchBlur() {
566
+ if (!this.searchControl.value) {
567
+ this.collapseSearch();
568
+ }
569
+ }
570
+ onSearchAnimationEnd() {
571
+ if (this.searchCollapsing()) {
572
+ this.searchState.set('collapsed');
573
+ this.searchControl.setValue('', { emitEvent: false });
574
+ }
575
+ }
576
+ collapseSearch() {
577
+ this.searchState.set('collapsing');
578
+ }
579
+ onButtonClick(event) {
580
+ const action = event.field.uiSettings?.buttonSettings?.action;
581
+ if (action === 'edit' && event.resource) {
582
+ this.pendingResource.set(event.resource);
583
+ this.editDialogOpen.set(true);
584
+ return;
585
+ }
586
+ if (action === 'delete' && event.resource) {
587
+ this.pendingResource.set(event.resource);
588
+ this.deleteDialogOpen.set(true);
589
+ return;
590
+ }
591
+ this.actionButtonClick.emit(event);
592
+ }
593
+ closeCreateDialog() {
594
+ this.createDialogOpen.set(false);
595
+ }
596
+ closeEditDialog() {
597
+ this.editDialogOpen.set(false);
598
+ }
599
+ closeDeleteDialog() {
600
+ this.deleteDialogOpen.set(false);
601
+ }
602
+ onCreateFieldChange(event) {
603
+ this.createFieldChange.emit(event);
604
+ }
605
+ onEditFieldChange(event) {
606
+ const resource = this.pendingResource();
607
+ if (resource) {
608
+ this.editFieldChange.emit({ resource, formChangeEvent: event });
609
+ }
610
+ }
611
+ onCreateSubmit(value) {
612
+ this.createSubmit.emit(value);
613
+ }
614
+ onEditSubmit(value) {
615
+ const resource = this.pendingResource();
616
+ if (resource) {
617
+ this.editSubmit.emit({ resource, value });
618
+ }
619
+ }
620
+ onDeleteSubmit() {
621
+ const resource = this.pendingResource();
622
+ if (resource)
623
+ this.deleteSubmit.emit(resource);
624
+ }
625
+ hasErrors(state) {
626
+ const errors = state.fieldErrors;
627
+ return !!errors && Object.values(errors).some((val) => !!val);
628
+ }
629
+ addActionsColumn() {
630
+ const cols = this.tableConfig().fields;
631
+ const buttonSettings = this.config().buttonSettings;
632
+ const editButton = this.editFormConfig();
633
+ const deleteButton = this.deleteConfirmationConfig();
634
+ const actions = [];
635
+ if (editButton) {
636
+ actions.push({
637
+ uiSettings: {
638
+ displayAs: 'button',
639
+ buttonSettings: {
640
+ icon: 'edit',
641
+ design: 'Transparent',
642
+ action: 'edit',
643
+ ...buttonSettings?.editButton,
644
+ },
645
+ },
646
+ group: { name: 'actions', label: '', multiline: false },
647
+ });
648
+ }
649
+ if (deleteButton) {
650
+ actions.push({
651
+ uiSettings: {
652
+ displayAs: 'button',
653
+ buttonSettings: {
654
+ icon: 'decline',
655
+ design: 'Transparent',
656
+ action: 'delete',
657
+ ...buttonSettings?.deleteButton,
658
+ },
659
+ },
660
+ group: { name: 'actions', label: '', multiline: false },
661
+ });
662
+ }
663
+ return cols.concat(actions);
664
+ }
665
+ buildInitialValues(fields, resource) {
666
+ if (!resource)
667
+ return {};
668
+ return fields.reduce((acc, field) => {
669
+ if (typeof field.name === 'string') {
670
+ acc[field.name] =
671
+ getResourceValueByJsonPath(resource, { property: field.name }) ??
672
+ '';
673
+ }
674
+ return acc;
675
+ }, {});
676
+ }
677
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: DeclarativeTableCard, deps: [], target: i0.ɵɵFactoryTarget.Component });
678
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: DeclarativeTableCard, isStandalone: true, selector: "mfp-declarative-table-card", inputs: { resources: { classPropertyName: "resources", publicName: "resources", isSignal: true, isRequired: true, transformFunction: null }, config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: true, transformFunction: null }, createFormState: { classPropertyName: "createFormState", publicName: "createFormState", isSignal: true, isRequired: false, transformFunction: null }, editFormState: { classPropertyName: "editFormState", publicName: "editFormState", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { actionButtonClick: "actionButtonClick", tableRowClicked: "tableRowClicked", loadMoreResources: "loadMoreResources", paginationLimitChanged: "paginationLimitChanged", searchChanged: "searchChanged", createFieldChange: "createFieldChange", editFieldChange: "editFieldChange", createSubmit: "createSubmit", editSubmit: "editSubmit", deleteSubmit: "deleteSubmit" }, viewQueries: [{ propertyName: "searchInputRef", first: true, predicate: ["searchInput"], descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"card\">\n <div class=\"card__header\">\n <div class=\"card__title\">\n @if (header(); as header) {\n {{ header }}\n @if (headerTooltip(); as tooltip) {\n <ui5-icon\n class=\"card__info-icon\"\n name=\"hint\"\n [showTooltip]=\"true\"\n [accessibleName]=\"tooltip\"\n />\n }\n }\n </div>\n <div class=\"card__actions\">\n @if (config().resourcesSearchable) {\n @if (searchExpanded()) {\n <ui5-input\n #searchInput\n [class]=\"\n 'card__search-input card__search-input--' +\n (searchCollapsing() ? 'leave' : 'enter')\n \"\n [formControl]=\"searchControl\"\n (blur)=\"onSearchBlur()\"\n (animationend)=\"onSearchAnimationEnd()\"\n />\n }\n <ui5-button\n class=\"card__search-btn\"\n design=\"Transparent\"\n [icon]=\"searchButtonConfig()?.icon ?? 'search'\"\n [tooltip]=\"searchButtonConfig()?.tooltip ?? 'Search'\"\n (click)=\"toggleSearch()\"\n />\n }\n @if (createFormConfig()) {\n <ui5-button\n class=\"card__create-btn\"\n [icon]=\"createButtonConfig()?.icon ?? 'add'\"\n [design]=\"createButtonConfig()?.design ?? 'Transparent'\"\n [tooltip]=\"createButtonConfig()?.tooltip\"\n (click)=\"createDialogOpen.set(true)\"\n >\n {{ createButtonConfig()?.text ?? '' }}\n </ui5-button>\n }\n </div>\n </div>\n\n <div class=\"card__body\">\n @if (tableConfig(); as config) {\n <mfp-declarative-table\n [columns]=\"effectiveColumns()\"\n [resources]=\"resources()\"\n [totalItemsCount]=\"config.totalItemsCount\"\n [paginationLimit]=\"config.paginationLimit ?? 5\"\n [hasMore]=\"config.hasMore ?? false\"\n (buttonClick)=\"onButtonClick($event)\"\n (tableRowClicked)=\"tableRowClicked.emit($event)\"\n (loadMoreResources)=\"loadMoreResources.emit()\"\n (paginationLimitChanged)=\"paginationLimitChanged.emit($event)\"\n />\n }\n </div>\n</div>\n\n@if (createFormConfig(); as config) {\n <ui5-dialog\n [open]=\"createDialogOpen()\"\n (ui5BeforeClose)=\"createDialogOpen.set(false)\"\n >\n <div slot=\"header\" class=\"dialog__header\">\n <ui5-title level=\"H5\">{{ config.title ?? 'Create' }}</ui5-title>\n </div>\n <div class=\"dialog__body\">\n <mfp-declarative-form\n #createForm\n [fields]=\"config.fields\"\n [fieldErrors]=\"createFormState().fieldErrors ?? {}\"\n (fieldChange)=\"onCreateFieldChange($event)\"\n (formSubmit)=\"onCreateSubmit($event)\"\n />\n </div>\n <div slot=\"footer\" class=\"dialog__footer\">\n <ui5-button\n design=\"Emphasized\"\n [disabled]=\"hasErrors(createFormState())\"\n (click)=\"createForm.submit()\"\n >\n {{ config.confirmLabel ?? 'Save' }}\n </ui5-button>\n <ui5-button design=\"Transparent\" (click)=\"closeCreateDialog()\">\n {{ config.cancelLabel ?? 'Cancel' }}\n </ui5-button>\n </div>\n </ui5-dialog>\n}\n\n@if (editFormConfig(); as config) {\n <ui5-dialog\n [open]=\"editDialogOpen()\"\n (ui5BeforeClose)=\"editDialogOpen.set(false)\"\n >\n <div slot=\"header\" class=\"dialog__header\">\n <ui5-title level=\"H5\">{{ config.title ?? 'Edit' }}</ui5-title>\n </div>\n <div class=\"dialog__body\">\n <mfp-declarative-form\n #editForm\n [fields]=\"config.fields\"\n [initialValues]=\"editInitialValue()\"\n [fieldErrors]=\"editFormState().fieldErrors ?? {}\"\n (fieldChange)=\"onEditFieldChange($event)\"\n (formSubmit)=\"onEditSubmit($event)\"\n />\n </div>\n <div slot=\"footer\" class=\"dialog__footer\">\n <ui5-button\n design=\"Emphasized\"\n [disabled]=\"hasErrors(editFormState())\"\n (click)=\"editForm.submit()\"\n >\n {{ config.confirmLabel ?? 'Edit' }}\n </ui5-button>\n <ui5-button design=\"Transparent\" (click)=\"closeEditDialog()\">\n {{ config.cancelLabel ?? 'Cancel' }}\n </ui5-button>\n </div>\n </ui5-dialog>\n}\n\n@if (deleteConfirmationConfig(); as config) {\n <ui5-dialog\n [open]=\"deleteDialogOpen()\"\n (ui5BeforeClose)=\"deleteDialogOpen.set(false)\"\n >\n <div slot=\"header\" class=\"dialog__header\">\n <ui5-title level=\"H5\">{{ config.title ?? 'Confirm Delete' }}</ui5-title>\n </div>\n @if (config.message) {\n <div class=\"dialog__body\">\n <p class=\"dialog__message\">{{ config.message }}</p>\n </div>\n }\n <div slot=\"footer\" class=\"dialog__footer\">\n <ui5-button design=\"Negative\" (click)=\"onDeleteSubmit()\">\n {{ config.confirmLabel ?? 'Delete' }}\n </ui5-button>\n <ui5-button design=\"Transparent\" (click)=\"closeDeleteDialog()\">\n {{ config.cancelLabel ?? 'Cancel' }}\n </ui5-button>\n </div>\n </ui5-dialog>\n}\n", styles: [":host{display:block}@keyframes slide-in{0%{opacity:0;transform:scaleX(0)}to{opacity:1;transform:scaleX(1)}}@keyframes slide-out{0%{opacity:1;transform:scaleX(1)}to{opacity:0;transform:scaleX(0)}}.card{display:flex;flex-direction:column;border:1px solid var(--sapGroup_ContentBorderColor, #d9d9d9);border-radius:16px;background:var(--sapGroup_TitleBackground, #fff)}.card__header{display:flex;align-items:center;justify-content:space-between;min-height:3rem;padding:0 1rem;border-bottom:1px solid var(--sapGroup_ContentBorderColor, #d9d9d9)}.card__title{color:var(--sapTile_TitleTextColor);text-overflow:ellipsis;font-family:var(--Font-Family-sapFontFamily, 72);font-size:var(--Font-Size-sapFontHeader6Size, 16px);font-style:normal;font-weight:700;line-height:normal;display:flex;align-items:center}.card__actions{display:flex;align-items:center;gap:.5rem}.card__info-icon{color:var(--sapButton_IconColor, #0070f2);margin-left:.5rem}.card__search-input{transform-origin:right center}.card__search-input--enter{animation:slide-in .2s ease-out both}.card__search-input--leave{animation:slide-out .2s ease-in both}.card__create-btn,.card__search-btn{min-width:auto;color:var(--sapButton_IconColor, #0070f2)}.card__body{flex:1;overflow:auto}.dialog__header{display:flex;align-items:flex-start;padding:.75rem 1rem}.dialog__body{margin:-1rem;min-width:320px;padding:1rem}.dialog__message{padding:1rem;margin:0;font-size:.875rem;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"], dependencies: [{ kind: "component", type: DeclarativeTable, selector: "mfp-declarative-table", inputs: ["columns", "resources", "trackByPath", "totalItemsCount", "paginationLimit", "hasMore"], outputs: ["buttonClick", "tableRowClicked", "loadMoreResources", "paginationLimitChanged"] }, { kind: "component", type: DeclarativeForm, selector: "mfp-declarative-form", inputs: ["fields", "initialValues", "fieldErrors"], outputs: ["fieldChange", "formSubmit"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "component", type: Dialog, selector: "ui5-dialog, [ui5-dialog]", inputs: ["headerText", "stretch", "draggable", "resizable", "state", "initialFocus", "preventFocusRestore", "accessibleName", "accessibleNameRef", "accessibleRole", "accessibleDescription", "accessibleDescriptionRef", "preventInitialFocus", "open"], outputs: ["ui5BeforeOpen", "ui5Open", "ui5BeforeClose", "ui5Close"], exportAs: ["ui5Dialog"] }, { kind: "component", type: Title, selector: "ui5-title, [ui5-title]", inputs: ["wrappingType", "level", "size"], exportAs: ["ui5Title"] }, { kind: "component", type: Button, selector: "ui5-button, [ui5-button]", inputs: ["design", "disabled", "icon", "endIcon", "submits", "tooltip", "accessibleName", "accessibleNameRef", "accessibilityAttributes", "accessibleDescription", "type", "accessibleRole", "loading", "loadingDelay"], outputs: ["ui5Click"], exportAs: ["ui5Button"] }, { kind: "component", type: Icon, selector: "ui5-icon, [ui5-icon]", inputs: ["design", "name", "accessibleName", "showTooltip", "mode"], outputs: ["ui5Click"], exportAs: ["ui5Icon"] }, { kind: "component", type: Input, selector: "ui5-input, [ui5-input]", inputs: ["disabled", "placeholder", "readonly", "required", "noTypeahead", "type", "value", "valueState", "name", "showSuggestions", "maxlength", "accessibleName", "accessibleNameRef", "accessibleDescription", "accessibleDescriptionRef", "showClearIcon", "open", "filter"], outputs: ["ui5Change", "ui5Input", "ui5Select", "ui5SelectionChange", "ui5Open", "ui5Close"], exportAs: ["ui5Input"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.ShadowDom });
679
+ }
680
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: DeclarativeTableCard, decorators: [{
681
+ type: Component,
682
+ args: [{ selector: 'mfp-declarative-table-card', imports: [
683
+ DeclarativeTable,
684
+ DeclarativeForm,
685
+ ReactiveFormsModule,
686
+ Dialog,
687
+ Title,
688
+ Button,
689
+ Icon,
690
+ Input,
691
+ ], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.ShadowDom, schemas: [CUSTOM_ELEMENTS_SCHEMA], template: "<div class=\"card\">\n <div class=\"card__header\">\n <div class=\"card__title\">\n @if (header(); as header) {\n {{ header }}\n @if (headerTooltip(); as tooltip) {\n <ui5-icon\n class=\"card__info-icon\"\n name=\"hint\"\n [showTooltip]=\"true\"\n [accessibleName]=\"tooltip\"\n />\n }\n }\n </div>\n <div class=\"card__actions\">\n @if (config().resourcesSearchable) {\n @if (searchExpanded()) {\n <ui5-input\n #searchInput\n [class]=\"\n 'card__search-input card__search-input--' +\n (searchCollapsing() ? 'leave' : 'enter')\n \"\n [formControl]=\"searchControl\"\n (blur)=\"onSearchBlur()\"\n (animationend)=\"onSearchAnimationEnd()\"\n />\n }\n <ui5-button\n class=\"card__search-btn\"\n design=\"Transparent\"\n [icon]=\"searchButtonConfig()?.icon ?? 'search'\"\n [tooltip]=\"searchButtonConfig()?.tooltip ?? 'Search'\"\n (click)=\"toggleSearch()\"\n />\n }\n @if (createFormConfig()) {\n <ui5-button\n class=\"card__create-btn\"\n [icon]=\"createButtonConfig()?.icon ?? 'add'\"\n [design]=\"createButtonConfig()?.design ?? 'Transparent'\"\n [tooltip]=\"createButtonConfig()?.tooltip\"\n (click)=\"createDialogOpen.set(true)\"\n >\n {{ createButtonConfig()?.text ?? '' }}\n </ui5-button>\n }\n </div>\n </div>\n\n <div class=\"card__body\">\n @if (tableConfig(); as config) {\n <mfp-declarative-table\n [columns]=\"effectiveColumns()\"\n [resources]=\"resources()\"\n [totalItemsCount]=\"config.totalItemsCount\"\n [paginationLimit]=\"config.paginationLimit ?? 5\"\n [hasMore]=\"config.hasMore ?? false\"\n (buttonClick)=\"onButtonClick($event)\"\n (tableRowClicked)=\"tableRowClicked.emit($event)\"\n (loadMoreResources)=\"loadMoreResources.emit()\"\n (paginationLimitChanged)=\"paginationLimitChanged.emit($event)\"\n />\n }\n </div>\n</div>\n\n@if (createFormConfig(); as config) {\n <ui5-dialog\n [open]=\"createDialogOpen()\"\n (ui5BeforeClose)=\"createDialogOpen.set(false)\"\n >\n <div slot=\"header\" class=\"dialog__header\">\n <ui5-title level=\"H5\">{{ config.title ?? 'Create' }}</ui5-title>\n </div>\n <div class=\"dialog__body\">\n <mfp-declarative-form\n #createForm\n [fields]=\"config.fields\"\n [fieldErrors]=\"createFormState().fieldErrors ?? {}\"\n (fieldChange)=\"onCreateFieldChange($event)\"\n (formSubmit)=\"onCreateSubmit($event)\"\n />\n </div>\n <div slot=\"footer\" class=\"dialog__footer\">\n <ui5-button\n design=\"Emphasized\"\n [disabled]=\"hasErrors(createFormState())\"\n (click)=\"createForm.submit()\"\n >\n {{ config.confirmLabel ?? 'Save' }}\n </ui5-button>\n <ui5-button design=\"Transparent\" (click)=\"closeCreateDialog()\">\n {{ config.cancelLabel ?? 'Cancel' }}\n </ui5-button>\n </div>\n </ui5-dialog>\n}\n\n@if (editFormConfig(); as config) {\n <ui5-dialog\n [open]=\"editDialogOpen()\"\n (ui5BeforeClose)=\"editDialogOpen.set(false)\"\n >\n <div slot=\"header\" class=\"dialog__header\">\n <ui5-title level=\"H5\">{{ config.title ?? 'Edit' }}</ui5-title>\n </div>\n <div class=\"dialog__body\">\n <mfp-declarative-form\n #editForm\n [fields]=\"config.fields\"\n [initialValues]=\"editInitialValue()\"\n [fieldErrors]=\"editFormState().fieldErrors ?? {}\"\n (fieldChange)=\"onEditFieldChange($event)\"\n (formSubmit)=\"onEditSubmit($event)\"\n />\n </div>\n <div slot=\"footer\" class=\"dialog__footer\">\n <ui5-button\n design=\"Emphasized\"\n [disabled]=\"hasErrors(editFormState())\"\n (click)=\"editForm.submit()\"\n >\n {{ config.confirmLabel ?? 'Edit' }}\n </ui5-button>\n <ui5-button design=\"Transparent\" (click)=\"closeEditDialog()\">\n {{ config.cancelLabel ?? 'Cancel' }}\n </ui5-button>\n </div>\n </ui5-dialog>\n}\n\n@if (deleteConfirmationConfig(); as config) {\n <ui5-dialog\n [open]=\"deleteDialogOpen()\"\n (ui5BeforeClose)=\"deleteDialogOpen.set(false)\"\n >\n <div slot=\"header\" class=\"dialog__header\">\n <ui5-title level=\"H5\">{{ config.title ?? 'Confirm Delete' }}</ui5-title>\n </div>\n @if (config.message) {\n <div class=\"dialog__body\">\n <p class=\"dialog__message\">{{ config.message }}</p>\n </div>\n }\n <div slot=\"footer\" class=\"dialog__footer\">\n <ui5-button design=\"Negative\" (click)=\"onDeleteSubmit()\">\n {{ config.confirmLabel ?? 'Delete' }}\n </ui5-button>\n <ui5-button design=\"Transparent\" (click)=\"closeDeleteDialog()\">\n {{ config.cancelLabel ?? 'Cancel' }}\n </ui5-button>\n </div>\n </ui5-dialog>\n}\n", styles: [":host{display:block}@keyframes slide-in{0%{opacity:0;transform:scaleX(0)}to{opacity:1;transform:scaleX(1)}}@keyframes slide-out{0%{opacity:1;transform:scaleX(1)}to{opacity:0;transform:scaleX(0)}}.card{display:flex;flex-direction:column;border:1px solid var(--sapGroup_ContentBorderColor, #d9d9d9);border-radius:16px;background:var(--sapGroup_TitleBackground, #fff)}.card__header{display:flex;align-items:center;justify-content:space-between;min-height:3rem;padding:0 1rem;border-bottom:1px solid var(--sapGroup_ContentBorderColor, #d9d9d9)}.card__title{color:var(--sapTile_TitleTextColor);text-overflow:ellipsis;font-family:var(--Font-Family-sapFontFamily, 72);font-size:var(--Font-Size-sapFontHeader6Size, 16px);font-style:normal;font-weight:700;line-height:normal;display:flex;align-items:center}.card__actions{display:flex;align-items:center;gap:.5rem}.card__info-icon{color:var(--sapButton_IconColor, #0070f2);margin-left:.5rem}.card__search-input{transform-origin:right center}.card__search-input--enter{animation:slide-in .2s ease-out both}.card__search-input--leave{animation:slide-out .2s ease-in both}.card__create-btn,.card__search-btn{min-width:auto;color:var(--sapButton_IconColor, #0070f2)}.card__body{flex:1;overflow:auto}.dialog__header{display:flex;align-items:flex-start;padding:.75rem 1rem}.dialog__body{margin:-1rem;min-width:320px;padding:1rem}.dialog__message{padding:1rem;margin:0;font-size:.875rem;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"] }]
692
+ }], ctorParameters: () => [], propDecorators: { resources: [{ type: i0.Input, args: [{ isSignal: true, alias: "resources", required: true }] }], config: [{ type: i0.Input, args: [{ isSignal: true, alias: "config", required: true }] }], createFormState: [{ type: i0.Input, args: [{ isSignal: true, alias: "createFormState", required: false }] }], editFormState: [{ type: i0.Input, args: [{ isSignal: true, alias: "editFormState", required: false }] }], actionButtonClick: [{ type: i0.Output, args: ["actionButtonClick"] }], tableRowClicked: [{ type: i0.Output, args: ["tableRowClicked"] }], loadMoreResources: [{ type: i0.Output, args: ["loadMoreResources"] }], paginationLimitChanged: [{ type: i0.Output, args: ["paginationLimitChanged"] }], searchChanged: [{ type: i0.Output, args: ["searchChanged"] }], createFieldChange: [{ type: i0.Output, args: ["createFieldChange"] }], editFieldChange: [{ type: i0.Output, args: ["editFieldChange"] }], createSubmit: [{ type: i0.Output, args: ["createSubmit"] }], editSubmit: [{ type: i0.Output, args: ["editSubmit"] }], deleteSubmit: [{ type: i0.Output, args: ["deleteSubmit"] }], searchInputRef: [{ type: i0.ViewChild, args: ['searchInput', { isSignal: true }] }] } });
693
+
694
+ class AddCardDialog {
695
+ availableCards = input([], ...(ngDevMode ? [{ debugName: "availableCards" }] : /* istanbul ignore next */ []));
696
+ addedCardsIds = input(new Set(), ...(ngDevMode ? [{ debugName: "addedCardsIds" }] : /* istanbul ignore next */ []));
697
+ open = input(false, ...(ngDevMode ? [{ debugName: "open" }] : /* istanbul ignore next */ []));
698
+ confirm = output();
699
+ cancel = output();
700
+ selectedIds = signal(new Set(), ...(ngDevMode ? [{ debugName: "selectedIds" }] : /* istanbul ignore next */ []));
701
+ constructor() {
702
+ effect(() => {
703
+ if (this.open()) {
704
+ this.selectedIds.set(new Set());
705
+ }
706
+ });
707
+ }
708
+ toggle(id) {
709
+ this.selectedIds.update((set) => {
710
+ const next = new Set(set);
711
+ if (next.has(id)) {
712
+ next.delete(id);
713
+ }
714
+ else {
715
+ next.add(id);
716
+ }
717
+ return next;
718
+ });
719
+ }
720
+ confirmAdd() {
721
+ const toAdd = this.availableCards().filter((ac) => this.selectedIds().has(ac.id) && !this.addedCardsIds().has(ac.id));
722
+ this.confirm.emit(toAdd);
723
+ }
724
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: AddCardDialog, deps: [], target: i0.ɵɵFactoryTarget.Component });
725
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: AddCardDialog, isStandalone: true, selector: "mfp-add-card-dialog", inputs: { availableCards: { classPropertyName: "availableCards", publicName: "availableCards", isSignal: true, isRequired: false, transformFunction: null }, addedCardsIds: { classPropertyName: "addedCardsIds", publicName: "addedCardsIds", isSignal: true, isRequired: false, transformFunction: null }, open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { confirm: "confirm", cancel: "cancel" }, ngImport: i0, template: "<ui5-dialog [open]=\"open()\" (ui5BeforeClose)=\"cancel.emit()\">\n <div slot=\"header\" class=\"add-card-dialog__header\">\n <ui5-title level=\"H5\">Add Card</ui5-title>\n </div>\n <div class=\"add-card-dialog\">\n @if (availableCards().length === 0) {\n <p class=\"add-card-dialog__empty\">No cards available.</p>\n }\n @for (ac of availableCards(); track ac.id) {\n @let alreadyAdded = addedCardsIds().has(ac.id);\n <div\n class=\"add-card-dialog__item\"\n [class.add-card-dialog__item--disabled]=\"alreadyAdded\"\n >\n <ui5-checkbox\n [checked]=\"alreadyAdded || selectedIds().has(ac.id)\"\n [disabled]=\"alreadyAdded\"\n (ui5Change)=\"toggle(ac.id)\"\n [text]=\"ac.label || ac.component\"\n />\n </div>\n }\n </div>\n <div slot=\"footer\" class=\"add-card-dialog__footer\">\n <ui5-button design=\"Emphasized\" (click)=\"confirmAdd()\">Add</ui5-button>\n <ui5-button design=\"Transparent\" (click)=\"cancel.emit()\">Cancel</ui5-button>\n </div>\n</ui5-dialog>\n", styles: [":host{display:contents}.add-card-dialog{margin:-1rem;min-width:300px}.add-card-dialog__header{display:flex;align-items:flex-start;padding:.75rem 1rem}.add-card-dialog__item{display:flex;align-items:center;padding:.5rem 1rem;border-bottom:1px solid var(--sapList_BorderColor, #e5e5e5)}.add-card-dialog__item:last-child{border-bottom:none}.add-card-dialog__item--disabled{opacity:.8}.add-card-dialog__empty{padding:1rem;margin:0;color:var(--sapContent_LabelColor, #6a6d70);font-size:.875rem}.add-card-dialog__footer{display:flex;justify-content:flex-end;gap:.5rem;padding:.5rem 1rem;margin:0 -1rem}\n"], dependencies: [{ kind: "component", type: Button, selector: "ui5-button, [ui5-button]", inputs: ["design", "disabled", "icon", "endIcon", "submits", "tooltip", "accessibleName", "accessibleNameRef", "accessibilityAttributes", "accessibleDescription", "type", "accessibleRole", "loading", "loadingDelay"], outputs: ["ui5Click"], exportAs: ["ui5Button"] }, { kind: "component", type: CheckBox, selector: "ui5-checkbox, [ui5-checkbox]", inputs: ["accessibleNameRef", "accessibleName", "disabled", "readonly", "displayOnly", "required", "indeterminate", "checked", "text", "valueState", "wrappingType", "name", "value"], outputs: ["ui5Change"], exportAs: ["ui5CheckBox"] }, { kind: "component", type: Dialog, selector: "ui5-dialog, [ui5-dialog]", inputs: ["headerText", "stretch", "draggable", "resizable", "state", "initialFocus", "preventFocusRestore", "accessibleName", "accessibleNameRef", "accessibleRole", "accessibleDescription", "accessibleDescriptionRef", "preventInitialFocus", "open"], outputs: ["ui5BeforeOpen", "ui5Open", "ui5BeforeClose", "ui5Close"], exportAs: ["ui5Dialog"] }, { kind: "component", type: Title, selector: "ui5-title, [ui5-title]", inputs: ["wrappingType", "level", "size"], exportAs: ["ui5Title"] }], encapsulation: i0.ViewEncapsulation.ShadowDom });
726
+ }
727
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: AddCardDialog, decorators: [{
728
+ type: Component,
729
+ args: [{ selector: 'mfp-add-card-dialog', encapsulation: ViewEncapsulation.ShadowDom, imports: [Button, CheckBox, Dialog, Title], template: "<ui5-dialog [open]=\"open()\" (ui5BeforeClose)=\"cancel.emit()\">\n <div slot=\"header\" class=\"add-card-dialog__header\">\n <ui5-title level=\"H5\">Add Card</ui5-title>\n </div>\n <div class=\"add-card-dialog\">\n @if (availableCards().length === 0) {\n <p class=\"add-card-dialog__empty\">No cards available.</p>\n }\n @for (ac of availableCards(); track ac.id) {\n @let alreadyAdded = addedCardsIds().has(ac.id);\n <div\n class=\"add-card-dialog__item\"\n [class.add-card-dialog__item--disabled]=\"alreadyAdded\"\n >\n <ui5-checkbox\n [checked]=\"alreadyAdded || selectedIds().has(ac.id)\"\n [disabled]=\"alreadyAdded\"\n (ui5Change)=\"toggle(ac.id)\"\n [text]=\"ac.label || ac.component\"\n />\n </div>\n }\n </div>\n <div slot=\"footer\" class=\"add-card-dialog__footer\">\n <ui5-button design=\"Emphasized\" (click)=\"confirmAdd()\">Add</ui5-button>\n <ui5-button design=\"Transparent\" (click)=\"cancel.emit()\">Cancel</ui5-button>\n </div>\n</ui5-dialog>\n", styles: [":host{display:contents}.add-card-dialog{margin:-1rem;min-width:300px}.add-card-dialog__header{display:flex;align-items:flex-start;padding:.75rem 1rem}.add-card-dialog__item{display:flex;align-items:center;padding:.5rem 1rem;border-bottom:1px solid var(--sapList_BorderColor, #e5e5e5)}.add-card-dialog__item:last-child{border-bottom:none}.add-card-dialog__item--disabled{opacity:.8}.add-card-dialog__empty{padding:1rem;margin:0;color:var(--sapContent_LabelColor, #6a6d70);font-size:.875rem}.add-card-dialog__footer{display:flex;justify-content:flex-end;gap:.5rem;padding:.5rem 1rem;margin:0 -1rem}\n"] }]
730
+ }], ctorParameters: () => [], propDecorators: { availableCards: [{ type: i0.Input, args: [{ isSignal: true, alias: "availableCards", required: false }] }], addedCardsIds: [{ type: i0.Input, args: [{ isSignal: true, alias: "addedCardsIds", required: false }] }], open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }], confirm: [{ type: i0.Output, args: ["confirm"] }], cancel: [{ type: i0.Output, args: ["cancel"] }] } });
731
+
732
+ const ELEMENT_SELECTOR_PATTERN = /^[a-z](?:[a-z0-9-]*)$/;
733
+ const dashboardCardRegistry = new Map();
734
+ function addComponentToRegistry(componentTypes) {
735
+ for (const componentType of componentTypes) {
736
+ const mirror = reflectComponentType(componentType);
737
+ if (!mirror) {
738
+ throw new Error(`Dashboard card registration failed: "${getTypeName(componentType)}" is not an Angular component.`);
739
+ }
740
+ if (!isStandalone(componentType)) {
741
+ throw new Error(`Dashboard card registration failed: "${mirror.selector}" must be a standalone Angular component.`);
742
+ }
743
+ const selector = toElementSelector(mirror.selector, componentType);
744
+ const existing = dashboardCardRegistry.get(selector);
745
+ if (existing && existing.componentType !== componentType) {
746
+ throw new Error(`Dashboard card registration failed: selector "${selector}" is already registered.`);
747
+ }
748
+ dashboardCardRegistry.set(selector, {
749
+ componentType,
750
+ selector,
751
+ inputs: createInputMap(mirror.inputs),
752
+ });
753
+ }
754
+ }
755
+ function getRegisteredDashboardCardComponent(selector) {
756
+ return dashboardCardRegistry.get(selector);
757
+ }
758
+ function resetDashboardCardRegistry() {
759
+ dashboardCardRegistry.clear();
760
+ }
761
+ function warnForUnknownDashboardCardInput(selector, inputName) {
762
+ if (!isDevMode())
763
+ return;
764
+ console.warn(`Dashboard card "${selector}" ignores unknown Angular input "${inputName}".`);
765
+ }
766
+ function createInputMap(inputs) {
767
+ const bindings = new Map();
768
+ for (const input of inputs) {
769
+ bindings.set(input.templateName, input.templateName);
770
+ bindings.set(input.propName, input.templateName);
771
+ }
772
+ return bindings;
773
+ }
774
+ function toElementSelector(selector, componentType) {
775
+ const normalized = selector.trim();
776
+ if (!ELEMENT_SELECTOR_PATTERN.test(normalized)) {
777
+ throw new Error(`Dashboard card registration failed: "${getTypeName(componentType)}" must use a single element selector. Received "${selector}".`);
778
+ }
779
+ return normalized;
780
+ }
781
+ function getTypeName(componentType) {
782
+ return componentType.name.replace(/^_+/, '') || 'AnonymousComponent';
783
+ }
784
+
785
+ const CARD_TYPES = {
786
+ WC: 'wc',
787
+ ANGULAR: 'angular',
788
+ SAP_UI: 'sap-ui',
789
+ };
790
+
791
+ function mountAngularCard(cfg, angularHost, onCleanup) {
792
+ const registeredComponent = getRegisteredDashboardCardComponent(cfg.component);
793
+ if (!registeredComponent) {
794
+ console.warn(`[DashboardCard] Angular component "${cfg.component}" is not registered`);
795
+ return;
796
+ }
797
+ const componentRef = angularHost.createComponent(registeredComponent.componentType);
798
+ for (const [inputName, value] of Object.entries(cfg.componentInputs ?? {})) {
799
+ const templateName = registeredComponent.inputs.get(inputName);
800
+ if (!templateName) {
801
+ warnForUnknownDashboardCardInput(cfg.component, inputName);
802
+ continue;
803
+ }
804
+ componentRef.setInput(templateName, value);
805
+ }
806
+ componentRef.changeDetectorRef.detectChanges();
807
+ onCleanup(() => {
808
+ angularHost.clear();
809
+ });
810
+ }
811
+
812
+ function mountSapCard(cfg, container, onCleanup) {
813
+ const host = container.element.nativeElement;
814
+ let sapContainer = null;
815
+ let isDestroyed = false;
816
+ const sapRequire = window.sap?.ui?.require;
817
+ if (sapRequire) {
818
+ sapRequire(['sap/ui/core/ComponentContainer'], (ComponentContainer) => {
819
+ if (isDestroyed)
820
+ return;
821
+ const container = new ComponentContainer({
822
+ name: cfg.component,
823
+ manifest: true,
824
+ async: true,
825
+ settings: cfg.componentInputs ?? {},
826
+ });
827
+ container.placeAt(host);
828
+ sapContainer = container;
829
+ });
830
+ }
831
+ else {
832
+ console.error('[DashboardCard] SAP UI5 is not available on window.sap');
833
+ }
834
+ onCleanup(() => {
835
+ isDestroyed = true;
836
+ sapContainer?.destroy();
837
+ host.innerHTML = '';
838
+ });
839
+ }
840
+
841
+ function mountWcCard(cfg, container, onCleanup, renderer) {
842
+ const host = container.element.nativeElement;
843
+ const element = renderer.createElement(cfg.component);
844
+ for (const [key, value] of Object.entries(cfg.componentInputs ?? {})) {
845
+ renderer.setProperty(element, key, value);
846
+ }
847
+ renderer.appendChild(host, element);
848
+ onCleanup(() => {
849
+ host.innerHTML = '';
850
+ });
851
+ }
852
+
853
+ class DashboardCard {
854
+ card = input.required(...(ngDevMode ? [{ debugName: "card" }] : /* istanbul ignore next */ []));
855
+ editMode = input(false, ...(ngDevMode ? [{ debugName: "editMode" }] : /* istanbul ignore next */ []));
856
+ removeCard = output();
857
+ gridColumn = computed(() => {
858
+ const width = this.card().w ?? 12;
859
+ return this.createGridTrack(this.card().x, width);
860
+ }, ...(ngDevMode ? [{ debugName: "gridColumn" }] : /* istanbul ignore next */ []));
861
+ gridRow = computed(() => {
862
+ const height = this.card().h ?? 100;
863
+ return this.createGridTrack(this.card().y, height);
864
+ }, ...(ngDevMode ? [{ debugName: "gridRow" }] : /* istanbul ignore next */ []));
865
+ host = viewChild('elementHost', { ...(ngDevMode ? { debugName: "host" } : /* istanbul ignore next */ {}), read: ViewContainerRef });
866
+ renderer = inject(Renderer2);
867
+ constructor() {
868
+ effect((onCleanup) => {
869
+ const host = this.host();
870
+ const cfg = this.card();
871
+ if (!host || !cfg.component)
872
+ return;
873
+ host.clear();
874
+ host.element.nativeElement.innerHTML = '';
875
+ switch (cfg.type) {
876
+ case CARD_TYPES.SAP_UI:
877
+ mountSapCard(cfg, host, onCleanup);
878
+ break;
879
+ case CARD_TYPES.ANGULAR:
880
+ mountAngularCard(cfg, host, onCleanup);
881
+ break;
882
+ case CARD_TYPES.WC:
883
+ default:
884
+ mountWcCard(cfg, host, onCleanup, this.renderer);
885
+ break;
886
+ }
887
+ });
888
+ }
889
+ createGridTrack(start, span) {
890
+ if (start === undefined) {
891
+ return `span ${span}`;
892
+ }
893
+ return `${start + 1} / span ${span}`;
894
+ }
895
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: DashboardCard, deps: [], target: i0.ɵɵFactoryTarget.Component });
896
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: DashboardCard, isStandalone: true, selector: "mfp-dashboard-card", inputs: { card: { classPropertyName: "card", publicName: "card", isSignal: true, isRequired: true, transformFunction: null }, editMode: { classPropertyName: "editMode", publicName: "editMode", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { removeCard: "removeCard" }, host: { properties: { "style.grid-column": "gridColumn()", "style.grid-row": "gridRow()" } }, viewQueries: [{ propertyName: "host", first: true, predicate: ["elementHost"], descendants: true, read: ViewContainerRef, isSignal: true }], ngImport: i0, template: "@if (card().component) {\n <div class=\"component-card\">\n @if (editMode()) {\n <div class=\"remove-wrapper\">\n <ui5-button\n class=\"card__remove\"\n design=\"Default\"\n icon=\"decline\"\n (click)=\"removeCard.emit()\"\n />\n </div>\n }\n <div\n [style.pointer-events]=\"editMode() ? 'none' : 'auto'\"\n class=\"component-host\"\n >\n <div #elementHost></div>\n </div>\n </div>\n} @else {\n <div class=\"card\">\n <div\n [style.pointer-events]=\"editMode() ? 'none' : 'auto'\"\n class=\"card__body\"\n >\n <ng-content />\n </div>\n </div>\n}", styles: [":host{display:block;min-width:0;min-height:0;overflow:visible}.card__remove{position:absolute;top:0;right:-.5rem;z-index:10;border-radius:50%!important;width:1.25rem!important;height:1.25rem!important;min-width:unset!important;padding:0!important;font-size:.625rem!important}.remove-wrapper{position:sticky;height:.5rem;top:0;right:0;z-index:10}.component-card{position:relative;height:100%;overflow:visible;isolation:isolate;padding:0 .5rem}.component-host{height:100%}.card{position:relative;display:flex;flex-direction:column;height:100%;border:1px solid var(--sapGroup_ContentBorderColor, #d9d9d9);border-radius:.5rem;background:var(--sapBackgroundColor, #fff);overflow:visible}.card__header{padding:.75rem 1rem;font-weight:600;border-bottom:1px solid var(--sapGroup_ContentBorderColor, #d9d9d9);background:var(--sapGroup_TitleBackground, #f5f5f5)}.card__body{flex:1;padding:1rem;overflow:auto}\n"], dependencies: [{ kind: "component", type: Button, selector: "ui5-button, [ui5-button]", inputs: ["design", "disabled", "icon", "endIcon", "submits", "tooltip", "accessibleName", "accessibleNameRef", "accessibilityAttributes", "accessibleDescription", "type", "accessibleRole", "loading", "loadingDelay"], outputs: ["ui5Click"], exportAs: ["ui5Button"] }] });
897
+ }
898
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: DashboardCard, decorators: [{
899
+ type: Component,
900
+ args: [{ selector: 'mfp-dashboard-card', imports: [Button], encapsulation: ViewEncapsulation.Emulated, host: {
901
+ '[style.grid-column]': 'gridColumn()',
902
+ '[style.grid-row]': 'gridRow()',
903
+ }, template: "@if (card().component) {\n <div class=\"component-card\">\n @if (editMode()) {\n <div class=\"remove-wrapper\">\n <ui5-button\n class=\"card__remove\"\n design=\"Default\"\n icon=\"decline\"\n (click)=\"removeCard.emit()\"\n />\n </div>\n }\n <div\n [style.pointer-events]=\"editMode() ? 'none' : 'auto'\"\n class=\"component-host\"\n >\n <div #elementHost></div>\n </div>\n </div>\n} @else {\n <div class=\"card\">\n <div\n [style.pointer-events]=\"editMode() ? 'none' : 'auto'\"\n class=\"card__body\"\n >\n <ng-content />\n </div>\n </div>\n}", styles: [":host{display:block;min-width:0;min-height:0;overflow:visible}.card__remove{position:absolute;top:0;right:-.5rem;z-index:10;border-radius:50%!important;width:1.25rem!important;height:1.25rem!important;min-width:unset!important;padding:0!important;font-size:.625rem!important}.remove-wrapper{position:sticky;height:.5rem;top:0;right:0;z-index:10}.component-card{position:relative;height:100%;overflow:visible;isolation:isolate;padding:0 .5rem}.component-host{height:100%}.card{position:relative;display:flex;flex-direction:column;height:100%;border:1px solid var(--sapGroup_ContentBorderColor, #d9d9d9);border-radius:.5rem;background:var(--sapBackgroundColor, #fff);overflow:visible}.card__header{padding:.75rem 1rem;font-weight:600;border-bottom:1px solid var(--sapGroup_ContentBorderColor, #d9d9d9);background:var(--sapGroup_TitleBackground, #f5f5f5)}.card__body{flex:1;padding:1rem;overflow:auto}\n"] }]
904
+ }], ctorParameters: () => [], propDecorators: { card: [{ type: i0.Input, args: [{ isSignal: true, alias: "card", required: true }] }], editMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "editMode", required: false }] }], removeCard: [{ type: i0.Output, args: ["removeCard"] }], host: [{ type: i0.ViewChild, args: ['elementHost', { ...{ read: ViewContainerRef }, isSignal: true }] }] } });
905
+
906
+ const COMPACT_BREAKPOINT = 726;
907
+ const CELL_HEIGHT = 10;
908
+
909
+ class DashboardSection {
910
+ section = input.required(...(ngDevMode ? [{ debugName: "section" }] : /* istanbul ignore next */ []));
911
+ cards = input([], ...(ngDevMode ? [{ debugName: "cards" }] : /* istanbul ignore next */ []));
912
+ columns = input(12, ...(ngDevMode ? [{ debugName: "columns" }] : /* istanbul ignore next */ []));
913
+ editMode = input(false, ...(ngDevMode ? [{ debugName: "editMode" }] : /* istanbul ignore next */ []));
914
+ removeSection = output();
915
+ removeCard = output();
916
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: DashboardSection, deps: [], target: i0.ɵɵFactoryTarget.Component });
917
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: DashboardSection, isStandalone: true, selector: "mfp-dashboard-section", inputs: { section: { classPropertyName: "section", publicName: "section", isSignal: true, isRequired: true, transformFunction: null }, cards: { classPropertyName: "cards", publicName: "cards", isSignal: true, isRequired: false, transformFunction: null }, columns: { classPropertyName: "columns", publicName: "columns", isSignal: true, isRequired: false, transformFunction: null }, editMode: { classPropertyName: "editMode", publicName: "editMode", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { removeSection: "removeSection", removeCard: "removeCard" }, host: { properties: { "style.grid-column": "\"span \" + (section().w ?? 12)" } }, ngImport: i0, template: "<div\n class=\"section\"\n [class.section--edit]=\"editMode() && section().editable !== false\"\n>\n @if (editMode() && section().editable !== false) {\n <ui5-button\n class=\"section__remove\"\n design=\"Default\"\n icon=\"decline\"\n (click)=\"removeSection.emit()\"\n >\n </ui5-button>\n }\n @if (section().title) {\n <div class=\"section__header\">\n <span class=\"section__title\">{{ section().title }}</span>\n </div>\n }\n <div class=\"section__grid\" [style.--cols]=\"columns()\">\n @for (card of cards(); track card.id) {\n <mfp-dashboard-card\n [card]=\"card\"\n [editMode]=\"editMode() && section().editable !== false\"\n (removeCard)=\"removeCard.emit($any(card.id))\"\n />\n }\n </div>\n</div>\n", styles: [":host{display:block;min-width:0}.section{position:relative;display:flex;flex-direction:column;border:1px solid transparent;border-top:none;border-radius:0 0 .5rem .5rem;padding:1.25rem .75rem .75rem}.section--edit{border-color:var(--sapGroup_ContentBorderColor, #d9d9d9)}.section__remove{position:absolute;top:-.75rem;right:-.75rem;border-radius:50%!important;width:1.25rem!important;height:1.25rem!important;min-width:unset!important;padding:0!important;font-size:.625rem!important}.section__header{position:absolute;top:0;left:0;right:0;transform:translateY(-50%);display:flex;align-items:center;pointer-events:none}.section__header:before,.section__header:after{content:\"\";flex:1;height:1px;background:transparent}.section__header:before{max-width:.75rem;flex:0 0 .75rem}.section--edit .section__header:before,.section--edit .section__header:after{background:var(--sapGroup_ContentBorderColor, #d9d9d9)}.section__title{font-size:1rem;font-weight:600;color:var(--sapTextColor, #fff);padding:0 .5rem;white-space:nowrap;pointer-events:all}.section__grid{display:grid;grid-template-columns:repeat(var(--cols, 12),1fr);grid-auto-rows:var(--row-height, 10px);gap:var(--row-gap, 0px)}@media(max-width:599px){.section__grid{grid-template-columns:1fr}}@media(min-width:600px)and (max-width:1023px){.section__grid{grid-template-columns:repeat(8,1fr)}}\n"], dependencies: [{ kind: "component", type: DashboardCard, selector: "mfp-dashboard-card", inputs: ["card", "editMode"], outputs: ["removeCard"] }, { kind: "component", type: Button, selector: "ui5-button, [ui5-button]", inputs: ["design", "disabled", "icon", "endIcon", "submits", "tooltip", "accessibleName", "accessibleNameRef", "accessibilityAttributes", "accessibleDescription", "type", "accessibleRole", "loading", "loadingDelay"], outputs: ["ui5Click"], exportAs: ["ui5Button"] }] });
918
+ }
919
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: DashboardSection, decorators: [{
920
+ type: Component,
921
+ args: [{ selector: 'mfp-dashboard-section', imports: [DashboardCard, Button], encapsulation: ViewEncapsulation.Emulated, host: {
922
+ '[style.grid-column]': '"span " + (section().w ?? 12)',
923
+ }, template: "<div\n class=\"section\"\n [class.section--edit]=\"editMode() && section().editable !== false\"\n>\n @if (editMode() && section().editable !== false) {\n <ui5-button\n class=\"section__remove\"\n design=\"Default\"\n icon=\"decline\"\n (click)=\"removeSection.emit()\"\n >\n </ui5-button>\n }\n @if (section().title) {\n <div class=\"section__header\">\n <span class=\"section__title\">{{ section().title }}</span>\n </div>\n }\n <div class=\"section__grid\" [style.--cols]=\"columns()\">\n @for (card of cards(); track card.id) {\n <mfp-dashboard-card\n [card]=\"card\"\n [editMode]=\"editMode() && section().editable !== false\"\n (removeCard)=\"removeCard.emit($any(card.id))\"\n />\n }\n </div>\n</div>\n", styles: [":host{display:block;min-width:0}.section{position:relative;display:flex;flex-direction:column;border:1px solid transparent;border-top:none;border-radius:0 0 .5rem .5rem;padding:1.25rem .75rem .75rem}.section--edit{border-color:var(--sapGroup_ContentBorderColor, #d9d9d9)}.section__remove{position:absolute;top:-.75rem;right:-.75rem;border-radius:50%!important;width:1.25rem!important;height:1.25rem!important;min-width:unset!important;padding:0!important;font-size:.625rem!important}.section__header{position:absolute;top:0;left:0;right:0;transform:translateY(-50%);display:flex;align-items:center;pointer-events:none}.section__header:before,.section__header:after{content:\"\";flex:1;height:1px;background:transparent}.section__header:before{max-width:.75rem;flex:0 0 .75rem}.section--edit .section__header:before,.section--edit .section__header:after{background:var(--sapGroup_ContentBorderColor, #d9d9d9)}.section__title{font-size:1rem;font-weight:600;color:var(--sapTextColor, #fff);padding:0 .5rem;white-space:nowrap;pointer-events:all}.section__grid{display:grid;grid-template-columns:repeat(var(--cols, 12),1fr);grid-auto-rows:var(--row-height, 10px);gap:var(--row-gap, 0px)}@media(max-width:599px){.section__grid{grid-template-columns:1fr}}@media(min-width:600px)and (max-width:1023px){.section__grid{grid-template-columns:repeat(8,1fr)}}\n"] }]
924
+ }], propDecorators: { section: [{ type: i0.Input, args: [{ isSignal: true, alias: "section", required: true }] }], cards: [{ type: i0.Input, args: [{ isSignal: true, alias: "cards", required: false }] }], columns: [{ type: i0.Input, args: [{ isSignal: true, alias: "columns", required: false }] }], editMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "editMode", required: false }] }], removeSection: [{ type: i0.Output, args: ["removeSection"] }], removeCard: [{ type: i0.Output, args: ["removeCard"] }] } });
925
+
926
+ document.body.classList.add('ui5-content-density-compact');
927
+ class Dashboard {
928
+ static registerAngularComponents(componentTypes) {
929
+ addComponentToRegistry(componentTypes);
930
+ }
931
+ config = input.required(...(ngDevMode ? [{ debugName: "config" }] : /* istanbul ignore next */ []));
932
+ sections = model([], ...(ngDevMode ? [{ debugName: "sections" }] : /* istanbul ignore next */ []));
933
+ cards = model([], ...(ngDevMode ? [{ debugName: "cards" }] : /* istanbul ignore next */ []));
934
+ availableCards = input([], ...(ngDevMode ? [{ debugName: "availableCards" }] : /* istanbul ignore next */ []));
935
+ saved = output();
936
+ actionButtonClick = output();
937
+ editMode = signal(false, ...(ngDevMode ? [{ debugName: "editMode" }] : /* istanbul ignore next */ []));
938
+ compactToolbar = signal(false, ...(ngDevMode ? [{ debugName: "compactToolbar" }] : /* istanbul ignore next */ []));
939
+ toolbarMenuOpen = signal(false, ...(ngDevMode ? [{ debugName: "toolbarMenuOpen" }] : /* istanbul ignore next */ []));
940
+ sectionsSnapshot = [];
941
+ cardsSnapshot = [];
942
+ gridStackItems = viewChild.required('grid');
943
+ resizeObserver;
944
+ hostEl = inject((ElementRef));
945
+ gridOptions = computed(() => ({
946
+ cellHeight: CELL_HEIGHT,
947
+ disableResize: !this.editMode(),
948
+ disableDrag: !this.editMode(),
949
+ columnOpts: {
950
+ breakpointForWindow: true,
951
+ breakpoints: [
952
+ { w: 1440, c: 12, layout: 'none' },
953
+ { w: 1024, c: 8, layout: 'compact' },
954
+ { w: 600, c: 1, layout: 'list' },
955
+ ],
956
+ },
957
+ }), ...(ngDevMode ? [{ debugName: "gridOptions" }] : /* istanbul ignore next */ []));
958
+ cardDialogOpen = signal(false, ...(ngDevMode ? [{ debugName: "cardDialogOpen" }] : /* istanbul ignore next */ []));
959
+ customActions = computed(() => this.config().customActions ?? [], ...(ngDevMode ? [{ debugName: "customActions" }] : /* istanbul ignore next */ []));
960
+ addedCardsIds = computed(() => new Set(this.cards().map((c) => c.id)), ...(ngDevMode ? [{ debugName: "addedCardsIds" }] : /* istanbul ignore next */ []));
961
+ editViewButton = computed(() => ({
962
+ icon: 'action-settings',
963
+ design: 'Transparent',
964
+ tooltip: 'Edit View',
965
+ text: '',
966
+ ...this.config().buttonsSettings?.editViewButton,
967
+ }), ...(ngDevMode ? [{ debugName: "editViewButton" }] : /* istanbul ignore next */ []));
968
+ addCardButton = computed(() => ({
969
+ icon: '',
970
+ design: 'Default',
971
+ tooltip: '',
972
+ text: '+ Add Card',
973
+ ...this.config().buttonsSettings?.addCardButton,
974
+ }), ...(ngDevMode ? [{ debugName: "addCardButton" }] : /* istanbul ignore next */ []));
975
+ sectionCards = computed(() => {
976
+ const all = this.cards();
977
+ return (sectionId) => all.filter((c) => c.sectionId === sectionId);
978
+ }, ...(ngDevMode ? [{ debugName: "sectionCards" }] : /* istanbul ignore next */ []));
979
+ cardsPosition = new Map();
980
+ looseCards = linkedSignal(() => this.cards().filter((c) => !c.sectionId), ...(ngDevMode ? [{ debugName: "looseCards" }] : /* istanbul ignore next */ []));
981
+ newGridStackNodes = [];
982
+ ngOnInit() {
983
+ this.resizeObserver = new ResizeObserver((entries) => {
984
+ const width = entries[0]?.contentRect.width ?? 0;
985
+ this.compactToolbar.set(width < COMPACT_BREAKPOINT);
986
+ });
987
+ this.resizeObserver.observe(this.hostEl.nativeElement);
988
+ }
989
+ ngOnDestroy() {
990
+ this.resizeObserver?.disconnect();
991
+ }
992
+ onMenuItemClick(actionId, event) {
993
+ if (actionId === 'edit-view') {
994
+ this.enterEditMode();
995
+ return;
996
+ }
997
+ const action = this.customActions().find((a) => a.action === actionId);
998
+ if (action) {
999
+ this.actionButtonClick.emit({ event: event, action });
1000
+ }
1001
+ }
1002
+ enterEditMode() {
1003
+ const gridStackNodes = this.gridStackItems()
1004
+ .gridstackItems?.toArray()
1005
+ .map((node) => node.options);
1006
+ if (gridStackNodes) {
1007
+ this.saveCardsPosition(gridStackNodes);
1008
+ }
1009
+ this.sectionsSnapshot = [...this.sections()];
1010
+ this.cardsSnapshot = [...this.cards()];
1011
+ this.editMode.set(true);
1012
+ }
1013
+ saveEdit() {
1014
+ this.saveCardsPosition(this.newGridStackNodes);
1015
+ this.saved.emit({
1016
+ sections: this.sections(),
1017
+ cards: this.cards().map((c) => {
1018
+ const pos = this.cardsPosition.get(c.id);
1019
+ return { ...c, x: pos?.x, y: pos?.y };
1020
+ }),
1021
+ });
1022
+ this.editMode.set(false);
1023
+ }
1024
+ cancelEdit() {
1025
+ this.sections.set(this.sectionsSnapshot);
1026
+ this.cards.set(this.cardsSnapshot.map((c) => {
1027
+ const pos = this.cardsPosition.get(c.id);
1028
+ return { ...c, x: pos?.x, y: pos?.y };
1029
+ }));
1030
+ this.cardDialogOpen.set(false);
1031
+ this.editMode.set(false);
1032
+ }
1033
+ removeSection(id) {
1034
+ this.sections.update((list) => list.filter((s) => s.id !== id));
1035
+ this.cards.update((list) => list.filter((c) => c.sectionId !== id));
1036
+ }
1037
+ removeCard(id) {
1038
+ this.cardsPosition.delete(id);
1039
+ this.cards.update((list) => list.filter((c) => c.id !== id));
1040
+ }
1041
+ openCardPanel() {
1042
+ this.cardDialogOpen.set(true);
1043
+ }
1044
+ closeCardPanel() {
1045
+ this.cardDialogOpen.set(false);
1046
+ }
1047
+ onCardsAdded(cards) {
1048
+ if (cards.length > 0) {
1049
+ this.cards.update((list) => [
1050
+ ...list,
1051
+ ...cards.map((ac) => ({
1052
+ ...ac,
1053
+ })),
1054
+ ]);
1055
+ }
1056
+ this.closeCardPanel();
1057
+ }
1058
+ onOrderChange(event) {
1059
+ this.newGridStackNodes = event.nodes;
1060
+ }
1061
+ saveCardsPosition(items) {
1062
+ items.forEach((node) => {
1063
+ if (node.id) {
1064
+ this.cardsPosition.set(node.id, { x: node.x, y: node.y });
1065
+ }
1066
+ });
1067
+ }
1068
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: Dashboard, deps: [], target: i0.ɵɵFactoryTarget.Component });
1069
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: Dashboard, isStandalone: true, selector: "mfp-dashboard", inputs: { config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: true, transformFunction: null }, sections: { classPropertyName: "sections", publicName: "sections", isSignal: true, isRequired: false, transformFunction: null }, cards: { classPropertyName: "cards", publicName: "cards", isSignal: true, isRequired: false, transformFunction: null }, availableCards: { classPropertyName: "availableCards", publicName: "availableCards", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { sections: "sectionsChange", cards: "cardsChange", saved: "saved", actionButtonClick: "actionButtonClick" }, host: { properties: { "style.background-image": "config().backgroundImageUrl ? \"url(\" + config().backgroundImageUrl + \")\" : null" } }, viewQueries: [{ propertyName: "gridStackItems", first: true, predicate: ["grid"], descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"mfp-dashboard\">\n <div class=\"mfp-dashboard__topbar\">\n <div class=\"mfp-dashboard__topbar-row\">\n <div class=\"mfp-dashboard__header\">\n @if (config().title) {\n <ui5-title level=\"H2\" size=\"H2\" wrapping-type=\"Normal\">{{\n config().title\n }}</ui5-title>\n }\n @if (config().description) {\n <ui5-text>{{ config().description }}</ui5-text>\n }\n </div>\n\n <div class=\"mfp-dashboard__toolbar\">\n @if (editMode()) {\n <ui5-button\n id=\"add-card-btn\"\n [design]=\"addCardButton().design\"\n [icon]=\"addCardButton().icon\"\n [tooltip]=\"addCardButton().tooltip\"\n (click)=\"openCardPanel()\"\n >{{ addCardButton().text }}</ui5-button\n >\n } @else if (compactToolbar()) {\n <ui5-button\n #menuBtn\n design=\"Transparent\"\n icon=\"menu2\"\n tooltip=\"Actions\"\n (click)=\"toolbarMenuOpen.update((v) => !v)\"\n ></ui5-button>\n <ui5-menu\n [open]=\"toolbarMenuOpen()\"\n [opener]=\"menuBtn.element\"\n (ui5Open)=\"toolbarMenuOpen.set(true)\"\n (ui5Close)=\"toolbarMenuOpen.set(false)\"\n (ui5ItemClick)=\"\n onMenuItemClick(\n $any($event).detail.item.dataset['action'],\n $event\n )\n \"\n >\n @for (action of customActions(); track action.action) {\n <ui5-menu-item\n [icon]=\"action.icon ?? ''\"\n [text]=\"action.text ?? ''\"\n [attr.data-action]=\"action.action\"\n ></ui5-menu-item>\n }\n @if (config().editable) {\n <ui5-menu-separator></ui5-menu-separator>\n <ui5-menu-item\n [icon]=\"editViewButton().icon\"\n [text]=\"editViewButton().text || 'Edit View'\"\n data-action=\"edit-view\"\n ></ui5-menu-item>\n }\n </ui5-menu>\n } @else {\n @for (action of customActions(); track action.action) {\n <ui5-button\n [design]=\"action.design ?? 'Default'\"\n [icon]=\"action.icon ?? ''\"\n [endIcon]=\"action?.endIcon\"\n [tooltip]=\"action.tooltip ?? ''\"\n (click)=\"\n actionButtonClick.emit({ event: $event, action: action })\n \"\n >{{ action.text }}</ui5-button\n >\n }\n @if (config().editable) {\n <ui5-button\n [design]=\"editViewButton().design\"\n [icon]=\"editViewButton().icon\"\n [tooltip]=\"editViewButton().tooltip\"\n (click)=\"enterEditMode()\"\n >{{ editViewButton().text }}</ui5-button\n >\n }\n }\n </div>\n </div>\n\n <div class=\"mfp-dashboard__subheader\">\n <slot name=\"dashboard-subheader\"></slot>\n </div>\n </div>\n <div class=\"mfp-sections-container\">\n @for (section of sections(); track section.id) {\n <mfp-dashboard-section\n [section]=\"section\"\n [cards]=\"sectionCards()(section.id)\"\n [columns]=\"12\"\n [editMode]=\"editMode()\"\n (removeSection)=\"removeSection(section.id)\"\n (removeCard)=\"removeCard($event)\"\n />\n }\n </div>\n <gridstack #grid (changeCB)=\"onOrderChange($event)\" [options]=\"gridOptions()\">\n @for (card of looseCards(); track card.id) {\n <gridstack-item\n [style.cursor]=\"editMode() ? 'pointer' : 'auto'\"\n [options]=\"card\"\n >\n <mfp-dashboard-card\n [card]=\"card\"\n [editMode]=\"editMode()\"\n (removeCard)=\"removeCard($any(card.id))\"\n />\n </gridstack-item>\n }\n </gridstack>\n\n @if (editMode()) {\n <div class=\"mfp-dashboard__edit-bar\">\n <ui5-button design=\"Emphasized\" (click)=\"saveEdit()\">Save</ui5-button>\n <ui5-button design=\"Transparent\" (click)=\"cancelEdit()\"\n >Cancel</ui5-button\n >\n </div>\n }\n</div>\n\n<mfp-add-card-dialog\n [open]=\"cardDialogOpen()\"\n [availableCards]=\"availableCards()\"\n [addedCardsIds]=\"addedCardsIds()\"\n (confirm)=\"onCardsAdded($event)\"\n (cancel)=\"closeCardPanel()\"\n/>\n", styles: [".grid-stack{position:relative}.grid-stack-rtl{direction:ltr}.grid-stack-rtl>.grid-stack-item{direction:rtl}.grid-stack-placeholder>.placeholder-content{background-color:#0000001a;margin:0;position:absolute;width:auto;z-index:0!important}.grid-stack>.grid-stack-item{position:absolute;padding:0;top:0;width:var(--gs-column-width);height:var(--gs-cell-height)}.grid-stack>.grid-stack-item>.grid-stack-item-content{margin:0;position:absolute;width:auto;overflow-x:hidden;overflow-y:auto}.grid-stack>.grid-stack-item.size-to-content:not(.size-to-content-max)>.grid-stack-item-content{overflow-y:hidden}.grid-stack:not(.grid-stack-rtl)>.grid-stack-item{left:0}.grid-stack.grid-stack-rtl>.grid-stack-item{right:0}.grid-stack>.grid-stack-item>.grid-stack-item-content,.grid-stack>.grid-stack-placeholder>.placeholder-content{top:var(--gs-item-margin-top);right:var(--gs-item-margin-right);bottom:var(--gs-item-margin-bottom);left:var(--gs-item-margin-left)}.grid-stack-item>.ui-resizable-handle{position:absolute;font-size:.1px;display:block;-ms-touch-action:none;touch-action:none}.grid-stack-item.ui-resizable-autohide>.ui-resizable-handle,.grid-stack-item.ui-resizable-disabled>.ui-resizable-handle{display:none}.grid-stack-item>.ui-resizable-ne,.grid-stack-item>.ui-resizable-nw,.grid-stack-item>.ui-resizable-se,.grid-stack-item>.ui-resizable-sw{background-image:url('data:image/svg+xml;utf8,<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" stroke=\"%23666\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" viewBox=\"0 0 20 20\"><path d=\"m10 3 2 2H8l2-2v14l-2-2h4l-2 2\"/></svg>');background-repeat:no-repeat;background-position:center}.grid-stack-item>.ui-resizable-ne{transform:rotate(45deg)}.grid-stack-item>.ui-resizable-sw{transform:rotate(45deg)}.grid-stack-item>.ui-resizable-nw{transform:rotate(-45deg)}.grid-stack-item>.ui-resizable-se{transform:rotate(-45deg)}.grid-stack-item>.ui-resizable-nw{cursor:nw-resize;width:20px;height:20px;top:var(--gs-item-margin-top);left:var(--gs-item-margin-left)}.grid-stack-item>.ui-resizable-n{cursor:n-resize;height:10px;top:var(--gs-item-margin-top);left:25px;right:25px}.grid-stack-item>.ui-resizable-ne{cursor:ne-resize;width:20px;height:20px;top:var(--gs-item-margin-top);right:var(--gs-item-margin-right)}.grid-stack-item>.ui-resizable-e{cursor:e-resize;width:10px;top:15px;bottom:15px;right:var(--gs-item-margin-right)}.grid-stack-item>.ui-resizable-se{cursor:se-resize;width:20px;height:20px;bottom:var(--gs-item-margin-bottom);right:var(--gs-item-margin-right)}.grid-stack-item>.ui-resizable-s{cursor:s-resize;height:10px;left:25px;bottom:var(--gs-item-margin-bottom);right:25px}.grid-stack-item>.ui-resizable-sw{cursor:sw-resize;width:20px;height:20px;bottom:var(--gs-item-margin-bottom);left:var(--gs-item-margin-left)}.grid-stack-item>.ui-resizable-w{cursor:w-resize;width:10px;top:15px;bottom:15px;left:var(--gs-item-margin-left)}.grid-stack-item.ui-draggable-dragging>.ui-resizable-handle{display:none!important}.grid-stack-item.ui-draggable-dragging{will-change:left,right,top}.grid-stack-item.ui-resizable-resizing{will-change:width,height}.ui-draggable-dragging,.ui-resizable-resizing{z-index:10000}.ui-draggable-dragging>.grid-stack-item-content,.ui-resizable-resizing>.grid-stack-item-content{box-shadow:1px 4px 6px #0003;opacity:.8}.grid-stack-animate,.grid-stack-animate .grid-stack-item{transition:left .3s,right .3s,top .3s,height .3s,width .3s}.grid-stack-animate .grid-stack-item.grid-stack-placeholder,.grid-stack-animate .grid-stack-item.ui-draggable-dragging,.grid-stack-animate .grid-stack-item.ui-resizable-resizing{transition:left 0s,right 0s,top 0s,height 0s,width 0s}.grid-stack>.grid-stack-item[gs-y=\"0\"]{top:0}.grid-stack:not(.grid-stack-rtl)>.grid-stack-item[gs-x=\"0\"]{left:0}.grid-stack.grid-stack-rtl>.grid-stack-item[gs-x=\"0\"]{right:0}mfp-dashboard,mfp-wc-dashboard{display:block;margin:0;padding:0;min-height:100%;background:var(--sapShellColor, #354a5e);background-size:cover;background-position:center;background-repeat:no-repeat;background-attachment:local}.mfp-dashboard{padding:2rem}.mfp-sections-container{display:grid;grid-template-columns:repeat(12,1fr);grid-auto-rows:auto;align-content:start;gap:1rem}@media(max-width:726px){.mfp-sections-container{grid-template-columns:1fr}}@media(min-width:727px)and (max-width:1200px){.mfp-sections-container{grid-template-columns:repeat(8,1fr)}}.mfp-dashboard__topbar{grid-column:1/-1;align-self:start;display:flex;flex-direction:column;gap:1rem;padding-bottom:3rem;width:100%;min-width:0}.mfp-dashboard__topbar-row{display:flex;align-items:flex-start;justify-content:space-between;gap:1rem}.mfp-dashboard__header{display:flex;flex-direction:column;gap:.25rem}.mfp-dashboard__toolbar{flex-shrink:0;display:flex;gap:.5rem}.mfp-dashboard__subheader{display:flex;align-items:center;gap:1rem}.mfp-dashboard__edit-bar{position:fixed;bottom:20px;left:16px;right:16px;z-index:1000;display:flex;justify-content:flex-end;align-items:center;gap:8px;padding:7px 16px;background:var(--sapPageFooter_Background, white);border-radius:var(--sapElement_BorderCornerRadius, 12px);box-shadow:0 0 0 1px #2235487a,0 2px 8px #2235484d;box-sizing:border-box;overflow:hidden}\n"], dependencies: [{ kind: "component", type: GridstackComponent, selector: "gridstack", inputs: ["options", "isEmpty"], outputs: ["addedCB", "changeCB", "disableCB", "dragCB", "dragStartCB", "dragStopCB", "droppedCB", "enableCB", "removedCB", "resizeCB", "resizeStartCB", "resizeStopCB"] }, { kind: "component", type: GridstackItemComponent, selector: "gridstack-item", inputs: ["options"] }, { kind: "component", type: AddCardDialog, selector: "mfp-add-card-dialog", inputs: ["availableCards", "addedCardsIds", "open"], outputs: ["confirm", "cancel"] }, { kind: "component", type: DashboardSection, selector: "mfp-dashboard-section", inputs: ["section", "cards", "columns", "editMode"], outputs: ["removeSection", "removeCard"] }, { kind: "component", type: DashboardCard, selector: "mfp-dashboard-card", inputs: ["card", "editMode"], outputs: ["removeCard"] }, { kind: "component", type: Button, selector: "ui5-button, [ui5-button]", inputs: ["design", "disabled", "icon", "endIcon", "submits", "tooltip", "accessibleName", "accessibleNameRef", "accessibilityAttributes", "accessibleDescription", "type", "accessibleRole", "loading", "loadingDelay"], outputs: ["ui5Click"], exportAs: ["ui5Button"] }, { kind: "component", type: Menu, selector: "ui5-menu, [ui5-menu]", inputs: ["headerText", "open", "placement", "horizontalAlign", "loading", "loadingDelay", "opener"], outputs: ["ui5ItemClick", "ui5BeforeOpen", "ui5Open", "ui5BeforeClose", "ui5Close"], exportAs: ["ui5Menu"] }, { kind: "component", type: MenuItem, selector: "ui5-menu-item, [ui5-menu-item]", inputs: ["text", "additionalText", "icon", "disabled", "loading", "loadingDelay", "accessibleName", "tooltip", "checked", "accessibilityAttributes", "type", "navigated", "highlight", "selected"], outputs: ["ui5BeforeOpen", "ui5Open", "ui5BeforeClose", "ui5Close", "ui5Check", "ui5DetailClick"], exportAs: ["ui5MenuItem"] }, { kind: "component", type: MenuSeparator, selector: "ui5-menu-separator, [ui5-menu-separator]", exportAs: ["ui5MenuSeparator"] }, { kind: "component", type: Title, selector: "ui5-title, [ui5-title]", inputs: ["wrappingType", "level", "size"], exportAs: ["ui5Title"] }, { kind: "component", type: Text, selector: "ui5-text, [ui5-text]", inputs: ["maxLines", "emptyIndicatorMode"], exportAs: ["ui5Text"] }], encapsulation: i0.ViewEncapsulation.None });
1070
+ }
1071
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: Dashboard, decorators: [{
1072
+ type: Component,
1073
+ args: [{ selector: 'mfp-dashboard', imports: [
1074
+ GridstackComponent,
1075
+ GridstackItemComponent,
1076
+ AddCardDialog,
1077
+ DashboardSection,
1078
+ DashboardCard,
1079
+ Button,
1080
+ Menu,
1081
+ MenuItem,
1082
+ MenuSeparator,
1083
+ Title,
1084
+ Text,
1085
+ ], encapsulation: ViewEncapsulation.None, host: {
1086
+ '[style.background-image]': 'config().backgroundImageUrl ? "url(" + config().backgroundImageUrl + ")" : null',
1087
+ }, template: "<div class=\"mfp-dashboard\">\n <div class=\"mfp-dashboard__topbar\">\n <div class=\"mfp-dashboard__topbar-row\">\n <div class=\"mfp-dashboard__header\">\n @if (config().title) {\n <ui5-title level=\"H2\" size=\"H2\" wrapping-type=\"Normal\">{{\n config().title\n }}</ui5-title>\n }\n @if (config().description) {\n <ui5-text>{{ config().description }}</ui5-text>\n }\n </div>\n\n <div class=\"mfp-dashboard__toolbar\">\n @if (editMode()) {\n <ui5-button\n id=\"add-card-btn\"\n [design]=\"addCardButton().design\"\n [icon]=\"addCardButton().icon\"\n [tooltip]=\"addCardButton().tooltip\"\n (click)=\"openCardPanel()\"\n >{{ addCardButton().text }}</ui5-button\n >\n } @else if (compactToolbar()) {\n <ui5-button\n #menuBtn\n design=\"Transparent\"\n icon=\"menu2\"\n tooltip=\"Actions\"\n (click)=\"toolbarMenuOpen.update((v) => !v)\"\n ></ui5-button>\n <ui5-menu\n [open]=\"toolbarMenuOpen()\"\n [opener]=\"menuBtn.element\"\n (ui5Open)=\"toolbarMenuOpen.set(true)\"\n (ui5Close)=\"toolbarMenuOpen.set(false)\"\n (ui5ItemClick)=\"\n onMenuItemClick(\n $any($event).detail.item.dataset['action'],\n $event\n )\n \"\n >\n @for (action of customActions(); track action.action) {\n <ui5-menu-item\n [icon]=\"action.icon ?? ''\"\n [text]=\"action.text ?? ''\"\n [attr.data-action]=\"action.action\"\n ></ui5-menu-item>\n }\n @if (config().editable) {\n <ui5-menu-separator></ui5-menu-separator>\n <ui5-menu-item\n [icon]=\"editViewButton().icon\"\n [text]=\"editViewButton().text || 'Edit View'\"\n data-action=\"edit-view\"\n ></ui5-menu-item>\n }\n </ui5-menu>\n } @else {\n @for (action of customActions(); track action.action) {\n <ui5-button\n [design]=\"action.design ?? 'Default'\"\n [icon]=\"action.icon ?? ''\"\n [endIcon]=\"action?.endIcon\"\n [tooltip]=\"action.tooltip ?? ''\"\n (click)=\"\n actionButtonClick.emit({ event: $event, action: action })\n \"\n >{{ action.text }}</ui5-button\n >\n }\n @if (config().editable) {\n <ui5-button\n [design]=\"editViewButton().design\"\n [icon]=\"editViewButton().icon\"\n [tooltip]=\"editViewButton().tooltip\"\n (click)=\"enterEditMode()\"\n >{{ editViewButton().text }}</ui5-button\n >\n }\n }\n </div>\n </div>\n\n <div class=\"mfp-dashboard__subheader\">\n <slot name=\"dashboard-subheader\"></slot>\n </div>\n </div>\n <div class=\"mfp-sections-container\">\n @for (section of sections(); track section.id) {\n <mfp-dashboard-section\n [section]=\"section\"\n [cards]=\"sectionCards()(section.id)\"\n [columns]=\"12\"\n [editMode]=\"editMode()\"\n (removeSection)=\"removeSection(section.id)\"\n (removeCard)=\"removeCard($event)\"\n />\n }\n </div>\n <gridstack #grid (changeCB)=\"onOrderChange($event)\" [options]=\"gridOptions()\">\n @for (card of looseCards(); track card.id) {\n <gridstack-item\n [style.cursor]=\"editMode() ? 'pointer' : 'auto'\"\n [options]=\"card\"\n >\n <mfp-dashboard-card\n [card]=\"card\"\n [editMode]=\"editMode()\"\n (removeCard)=\"removeCard($any(card.id))\"\n />\n </gridstack-item>\n }\n </gridstack>\n\n @if (editMode()) {\n <div class=\"mfp-dashboard__edit-bar\">\n <ui5-button design=\"Emphasized\" (click)=\"saveEdit()\">Save</ui5-button>\n <ui5-button design=\"Transparent\" (click)=\"cancelEdit()\"\n >Cancel</ui5-button\n >\n </div>\n }\n</div>\n\n<mfp-add-card-dialog\n [open]=\"cardDialogOpen()\"\n [availableCards]=\"availableCards()\"\n [addedCardsIds]=\"addedCardsIds()\"\n (confirm)=\"onCardsAdded($event)\"\n (cancel)=\"closeCardPanel()\"\n/>\n", styles: [".grid-stack{position:relative}.grid-stack-rtl{direction:ltr}.grid-stack-rtl>.grid-stack-item{direction:rtl}.grid-stack-placeholder>.placeholder-content{background-color:#0000001a;margin:0;position:absolute;width:auto;z-index:0!important}.grid-stack>.grid-stack-item{position:absolute;padding:0;top:0;width:var(--gs-column-width);height:var(--gs-cell-height)}.grid-stack>.grid-stack-item>.grid-stack-item-content{margin:0;position:absolute;width:auto;overflow-x:hidden;overflow-y:auto}.grid-stack>.grid-stack-item.size-to-content:not(.size-to-content-max)>.grid-stack-item-content{overflow-y:hidden}.grid-stack:not(.grid-stack-rtl)>.grid-stack-item{left:0}.grid-stack.grid-stack-rtl>.grid-stack-item{right:0}.grid-stack>.grid-stack-item>.grid-stack-item-content,.grid-stack>.grid-stack-placeholder>.placeholder-content{top:var(--gs-item-margin-top);right:var(--gs-item-margin-right);bottom:var(--gs-item-margin-bottom);left:var(--gs-item-margin-left)}.grid-stack-item>.ui-resizable-handle{position:absolute;font-size:.1px;display:block;-ms-touch-action:none;touch-action:none}.grid-stack-item.ui-resizable-autohide>.ui-resizable-handle,.grid-stack-item.ui-resizable-disabled>.ui-resizable-handle{display:none}.grid-stack-item>.ui-resizable-ne,.grid-stack-item>.ui-resizable-nw,.grid-stack-item>.ui-resizable-se,.grid-stack-item>.ui-resizable-sw{background-image:url('data:image/svg+xml;utf8,<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" stroke=\"%23666\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" viewBox=\"0 0 20 20\"><path d=\"m10 3 2 2H8l2-2v14l-2-2h4l-2 2\"/></svg>');background-repeat:no-repeat;background-position:center}.grid-stack-item>.ui-resizable-ne{transform:rotate(45deg)}.grid-stack-item>.ui-resizable-sw{transform:rotate(45deg)}.grid-stack-item>.ui-resizable-nw{transform:rotate(-45deg)}.grid-stack-item>.ui-resizable-se{transform:rotate(-45deg)}.grid-stack-item>.ui-resizable-nw{cursor:nw-resize;width:20px;height:20px;top:var(--gs-item-margin-top);left:var(--gs-item-margin-left)}.grid-stack-item>.ui-resizable-n{cursor:n-resize;height:10px;top:var(--gs-item-margin-top);left:25px;right:25px}.grid-stack-item>.ui-resizable-ne{cursor:ne-resize;width:20px;height:20px;top:var(--gs-item-margin-top);right:var(--gs-item-margin-right)}.grid-stack-item>.ui-resizable-e{cursor:e-resize;width:10px;top:15px;bottom:15px;right:var(--gs-item-margin-right)}.grid-stack-item>.ui-resizable-se{cursor:se-resize;width:20px;height:20px;bottom:var(--gs-item-margin-bottom);right:var(--gs-item-margin-right)}.grid-stack-item>.ui-resizable-s{cursor:s-resize;height:10px;left:25px;bottom:var(--gs-item-margin-bottom);right:25px}.grid-stack-item>.ui-resizable-sw{cursor:sw-resize;width:20px;height:20px;bottom:var(--gs-item-margin-bottom);left:var(--gs-item-margin-left)}.grid-stack-item>.ui-resizable-w{cursor:w-resize;width:10px;top:15px;bottom:15px;left:var(--gs-item-margin-left)}.grid-stack-item.ui-draggable-dragging>.ui-resizable-handle{display:none!important}.grid-stack-item.ui-draggable-dragging{will-change:left,right,top}.grid-stack-item.ui-resizable-resizing{will-change:width,height}.ui-draggable-dragging,.ui-resizable-resizing{z-index:10000}.ui-draggable-dragging>.grid-stack-item-content,.ui-resizable-resizing>.grid-stack-item-content{box-shadow:1px 4px 6px #0003;opacity:.8}.grid-stack-animate,.grid-stack-animate .grid-stack-item{transition:left .3s,right .3s,top .3s,height .3s,width .3s}.grid-stack-animate .grid-stack-item.grid-stack-placeholder,.grid-stack-animate .grid-stack-item.ui-draggable-dragging,.grid-stack-animate .grid-stack-item.ui-resizable-resizing{transition:left 0s,right 0s,top 0s,height 0s,width 0s}.grid-stack>.grid-stack-item[gs-y=\"0\"]{top:0}.grid-stack:not(.grid-stack-rtl)>.grid-stack-item[gs-x=\"0\"]{left:0}.grid-stack.grid-stack-rtl>.grid-stack-item[gs-x=\"0\"]{right:0}mfp-dashboard,mfp-wc-dashboard{display:block;margin:0;padding:0;min-height:100%;background:var(--sapShellColor, #354a5e);background-size:cover;background-position:center;background-repeat:no-repeat;background-attachment:local}.mfp-dashboard{padding:2rem}.mfp-sections-container{display:grid;grid-template-columns:repeat(12,1fr);grid-auto-rows:auto;align-content:start;gap:1rem}@media(max-width:726px){.mfp-sections-container{grid-template-columns:1fr}}@media(min-width:727px)and (max-width:1200px){.mfp-sections-container{grid-template-columns:repeat(8,1fr)}}.mfp-dashboard__topbar{grid-column:1/-1;align-self:start;display:flex;flex-direction:column;gap:1rem;padding-bottom:3rem;width:100%;min-width:0}.mfp-dashboard__topbar-row{display:flex;align-items:flex-start;justify-content:space-between;gap:1rem}.mfp-dashboard__header{display:flex;flex-direction:column;gap:.25rem}.mfp-dashboard__toolbar{flex-shrink:0;display:flex;gap:.5rem}.mfp-dashboard__subheader{display:flex;align-items:center;gap:1rem}.mfp-dashboard__edit-bar{position:fixed;bottom:20px;left:16px;right:16px;z-index:1000;display:flex;justify-content:flex-end;align-items:center;gap:8px;padding:7px 16px;background:var(--sapPageFooter_Background, white);border-radius:var(--sapElement_BorderCornerRadius, 12px);box-shadow:0 0 0 1px #2235487a,0 2px 8px #2235484d;box-sizing:border-box;overflow:hidden}\n"] }]
1088
+ }], propDecorators: { config: [{ type: i0.Input, args: [{ isSignal: true, alias: "config", required: true }] }], sections: [{ type: i0.Input, args: [{ isSignal: true, alias: "sections", required: false }] }, { type: i0.Output, args: ["sectionsChange"] }], cards: [{ type: i0.Input, args: [{ isSignal: true, alias: "cards", required: false }] }, { type: i0.Output, args: ["cardsChange"] }], availableCards: [{ type: i0.Input, args: [{ isSignal: true, alias: "availableCards", required: false }] }], saved: [{ type: i0.Output, args: ["saved"] }], actionButtonClick: [{ type: i0.Output, args: ["actionButtonClick"] }], gridStackItems: [{ type: i0.ViewChild, args: ['grid', { isSignal: true }] }] } });
1089
+
1090
+ class VisitedServiceCard {
1091
+ serviceType = input.required(...(ngDevMode ? [{ debugName: "serviceType" }] : /* istanbul ignore next */ []));
1092
+ serviceName = input.required(...(ngDevMode ? [{ debugName: "serviceName" }] : /* istanbul ignore next */ []));
1093
+ serviceDescription = input.required(...(ngDevMode ? [{ debugName: "serviceDescription" }] : /* istanbul ignore next */ []));
1094
+ serviceIcon = input.required(...(ngDevMode ? [{ debugName: "serviceIcon" }] : /* istanbul ignore next */ []));
1095
+ path = input.required(...(ngDevMode ? [{ debugName: "path" }] : /* istanbul ignore next */ []));
1096
+ click = output();
1097
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: VisitedServiceCard, deps: [], target: i0.ɵɵFactoryTarget.Component });
1098
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.12", type: VisitedServiceCard, isStandalone: true, selector: "mfp-visited-service-card", inputs: { serviceType: { classPropertyName: "serviceType", publicName: "serviceType", isSignal: true, isRequired: true, transformFunction: null }, serviceName: { classPropertyName: "serviceName", publicName: "serviceName", isSignal: true, isRequired: true, transformFunction: null }, serviceDescription: { classPropertyName: "serviceDescription", publicName: "serviceDescription", isSignal: true, isRequired: true, transformFunction: null }, serviceIcon: { classPropertyName: "serviceIcon", publicName: "serviceIcon", isSignal: true, isRequired: true, transformFunction: null }, path: { classPropertyName: "path", publicName: "path", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { click: "click" }, ngImport: i0, template: "<div class=\"visited-card-wrapper\" (click)=\"click.emit(path())\">\n <span class=\"visited-card__type-badge\">{{ serviceType() }}</span>\n <ui5-card class=\"visited-card\">\n <ui5-card-header\n slot=\"header\"\n [titleText]=\"serviceName()\"\n [subtitleText]=\"serviceDescription()\"\n [interactive]=\"true\"\n >\n <ui5-icon\n slot=\"avatar\"\n [name]=\"serviceIcon()\"\n class=\"visited-card__icon\"\n ></ui5-icon>\n </ui5-card-header>\n </ui5-card>\n</div>\n", styles: [":host{display:block}.visited-card-wrapper{position:relative;padding-top:.625rem;cursor:pointer}.visited-card{width:100%}.visited-card__type-badge{position:absolute;top:0;right:.75rem;display:inline-block;padding:.125rem .625rem;border-radius:.75rem;background-color:#e8f3ff;color:#0070f2;font-size:var(--sapFontSmallSize, .75rem);font-weight:700;white-space:nowrap;z-index:1}.visited-card__icon{width:2.5rem;height:2.5rem;color:var(--sapHighlightColor, #0070f2)}\n"], dependencies: [{ kind: "component", type: Card, selector: "ui5-card, [ui5-card]", inputs: ["accessibleName", "accessibleNameRef", "loading", "loadingDelay"], exportAs: ["ui5Card"] }, { kind: "component", type: CardHeader, selector: "ui5-card-header, [ui5-card-header]", inputs: ["titleText", "subtitleText", "additionalText", "interactive"], outputs: ["ui5Click"], exportAs: ["ui5CardHeader"] }, { kind: "component", type: Icon, selector: "ui5-icon, [ui5-icon]", inputs: ["design", "name", "accessibleName", "showTooltip", "mode"], outputs: ["ui5Click"], exportAs: ["ui5Icon"] }], encapsulation: i0.ViewEncapsulation.ShadowDom });
1099
+ }
1100
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: VisitedServiceCard, decorators: [{
1101
+ type: Component,
1102
+ args: [{ selector: 'mfp-visited-service-card', encapsulation: ViewEncapsulation.ShadowDom, imports: [Card, CardHeader, Icon], template: "<div class=\"visited-card-wrapper\" (click)=\"click.emit(path())\">\n <span class=\"visited-card__type-badge\">{{ serviceType() }}</span>\n <ui5-card class=\"visited-card\">\n <ui5-card-header\n slot=\"header\"\n [titleText]=\"serviceName()\"\n [subtitleText]=\"serviceDescription()\"\n [interactive]=\"true\"\n >\n <ui5-icon\n slot=\"avatar\"\n [name]=\"serviceIcon()\"\n class=\"visited-card__icon\"\n ></ui5-icon>\n </ui5-card-header>\n </ui5-card>\n</div>\n", styles: [":host{display:block}.visited-card-wrapper{position:relative;padding-top:.625rem;cursor:pointer}.visited-card{width:100%}.visited-card__type-badge{position:absolute;top:0;right:.75rem;display:inline-block;padding:.125rem .625rem;border-radius:.75rem;background-color:#e8f3ff;color:#0070f2;font-size:var(--sapFontSmallSize, .75rem);font-weight:700;white-space:nowrap;z-index:1}.visited-card__icon{width:2.5rem;height:2.5rem;color:var(--sapHighlightColor, #0070f2)}\n"] }]
1103
+ }], propDecorators: { serviceType: [{ type: i0.Input, args: [{ isSignal: true, alias: "serviceType", required: true }] }], serviceName: [{ type: i0.Input, args: [{ isSignal: true, alias: "serviceName", required: true }] }], serviceDescription: [{ type: i0.Input, args: [{ isSignal: true, alias: "serviceDescription", required: true }] }], serviceIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "serviceIcon", required: true }] }], path: [{ type: i0.Input, args: [{ isSignal: true, alias: "path", required: true }] }], click: [{ type: i0.Output, args: ["click"] }] } });
1104
+
1105
+ class Favorites {
1106
+ items = [
1107
+ { label: 'Create Account', icon: 'add', action: 'create-account' },
1108
+ { label: 'Start Approval', icon: 'workflow-tasks', action: 'start-approval' },
1109
+ { label: 'Add User to Account', icon: 'person-placeholder', action: 'add-user' },
1110
+ ];
1111
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: Favorites, deps: [], target: i0.ɵɵFactoryTarget.Component });
1112
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: Favorites, isStandalone: true, selector: "mfp-favorites", ngImport: i0, template: "<div class=\"favorites\">\n <ui5-title level=\"H5\" class=\"favorites__title\">Favorites</ui5-title>\n <div class=\"favorites__list\">\n @for (item of items; track item.action) {\n <div class=\"favorites__item\">\n <ui5-icon name=\"{{ item.icon }}\" class=\"favorites__icon\"></ui5-icon>\n <span class=\"favorites__label\">{{ item.label }}</span>\n <ui5-button design=\"Transparent\" icon=\"{{ item.icon }}\">{{ item.label }}</ui5-button>\n </div>\n }\n </div>\n</div>\n", styles: [":host{display:block;height:100%}.favorites{display:flex;flex-direction:column;height:100%;background:var(--sapTile_Background, #fff);border-radius:var(--_ui5_card_border-radius, .5rem);border:var(--_ui5_card_border, 1px solid var(--sapTile_BorderColor, #e5e5e5));box-shadow:var(--_ui5_card_box_shadow, var(--sapContent_Shadow0));overflow:hidden}.favorites__title{padding:1rem 1rem .5rem;flex-shrink:0}.favorites__list{display:flex;flex-direction:column;flex:1;overflow-y:auto}.favorites__item{display:flex;align-items:center;gap:.75rem;padding:.5rem 1rem;border-bottom:1px solid var(--sapList_BorderColor, #e5e5e5)}.favorites__item:last-child{border-bottom:none}.favorites__icon{flex-shrink:0;color:var(--sapHighlightColor, #0070f2)}.favorites__label{flex:1;font-size:var(--sapFontSize, .875rem);color:var(--sapTextColor, #32363a)}\n"], dependencies: [{ kind: "component", type: Button, selector: "ui5-button, [ui5-button]", inputs: ["design", "disabled", "icon", "endIcon", "submits", "tooltip", "accessibleName", "accessibleNameRef", "accessibilityAttributes", "accessibleDescription", "type", "accessibleRole", "loading", "loadingDelay"], outputs: ["ui5Click"], exportAs: ["ui5Button"] }, { kind: "component", type: Icon, selector: "ui5-icon, [ui5-icon]", inputs: ["design", "name", "accessibleName", "showTooltip", "mode"], outputs: ["ui5Click"], exportAs: ["ui5Icon"] }, { kind: "component", type: Title, selector: "ui5-title, [ui5-title]", inputs: ["wrappingType", "level", "size"], exportAs: ["ui5Title"] }], encapsulation: i0.ViewEncapsulation.ShadowDom });
1113
+ }
1114
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: Favorites, decorators: [{
1115
+ type: Component,
1116
+ args: [{ selector: 'mfp-favorites', encapsulation: ViewEncapsulation.ShadowDom, imports: [Button, Icon, Title], template: "<div class=\"favorites\">\n <ui5-title level=\"H5\" class=\"favorites__title\">Favorites</ui5-title>\n <div class=\"favorites__list\">\n @for (item of items; track item.action) {\n <div class=\"favorites__item\">\n <ui5-icon name=\"{{ item.icon }}\" class=\"favorites__icon\"></ui5-icon>\n <span class=\"favorites__label\">{{ item.label }}</span>\n <ui5-button design=\"Transparent\" icon=\"{{ item.icon }}\">{{ item.label }}</ui5-button>\n </div>\n }\n </div>\n</div>\n", styles: [":host{display:block;height:100%}.favorites{display:flex;flex-direction:column;height:100%;background:var(--sapTile_Background, #fff);border-radius:var(--_ui5_card_border-radius, .5rem);border:var(--_ui5_card_border, 1px solid var(--sapTile_BorderColor, #e5e5e5));box-shadow:var(--_ui5_card_box_shadow, var(--sapContent_Shadow0));overflow:hidden}.favorites__title{padding:1rem 1rem .5rem;flex-shrink:0}.favorites__list{display:flex;flex-direction:column;flex:1;overflow-y:auto}.favorites__item{display:flex;align-items:center;gap:.75rem;padding:.5rem 1rem;border-bottom:1px solid var(--sapList_BorderColor, #e5e5e5)}.favorites__item:last-child{border-bottom:none}.favorites__icon{flex-shrink:0;color:var(--sapHighlightColor, #0070f2)}.favorites__label{flex:1;font-size:var(--sapFontSize, .875rem);color:var(--sapTextColor, #32363a)}\n"] }]
1117
+ }] });
1118
+
1119
+ class ServiceStatusCard {
1120
+ services = [
1121
+ { name: 'API Gateway', icon: 'connected', status: 'operational' },
1122
+ { name: 'Identity Service', icon: 'customer', status: 'operational' },
1123
+ { name: 'Database Cluster', icon: 'database', status: 'degraded' },
1124
+ { name: 'Message Queue', icon: 'email', status: 'operational' },
1125
+ { name: 'Object Storage', icon: 'storage', status: 'maintenance' },
1126
+ { name: 'Audit Log', icon: 'log', status: 'outage' },
1127
+ ];
1128
+ statusConfig = {
1129
+ operational: { label: 'Operational', icon: 'status-positive', colorClass: 'status--operational' },
1130
+ degraded: { label: 'Degraded', icon: 'status-critical', colorClass: 'status--degraded' },
1131
+ outage: { label: 'Outage', icon: 'status-negative', colorClass: 'status--outage' },
1132
+ maintenance: { label: 'Maintenance', icon: 'status-inactive', colorClass: 'status--maintenance' },
1133
+ };
1134
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: ServiceStatusCard, deps: [], target: i0.ɵɵFactoryTarget.Component });
1135
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: ServiceStatusCard, isStandalone: true, selector: "mfp-service-status-card", ngImport: i0, template: "<div class=\"service-status\">\n <ui5-title level=\"H5\" class=\"service-status__title\">Service Availability</ui5-title>\n <div class=\"service-status__list\">\n @for (service of services; track service.name) {\n <div class=\"service-status__item\">\n <ui5-icon name=\"{{ service.icon }}\" class=\"service-status__icon\"></ui5-icon>\n <span class=\"service-status__name\">{{ service.name }}</span>\n <div class=\"service-status__status {{ statusConfig[service.status].colorClass }}\">\n <ui5-icon name=\"{{ statusConfig[service.status].icon }}\" class=\"service-status__status-icon\"></ui5-icon>\n <span class=\"service-status__status-label\">{{ statusConfig[service.status].label }}</span>\n </div>\n </div>\n }\n </div>\n</div>\n", styles: [":host{display:block;height:100%}.service-status{display:flex;flex-direction:column;height:100%;background:var(--sapTile_Background, #fff);border-radius:var(--_ui5_card_border-radius, .5rem);border:var(--_ui5_card_border, 1px solid var(--sapTile_BorderColor, #e5e5e5));box-shadow:var(--_ui5_card_box_shadow, var(--sapContent_Shadow0));overflow:hidden}.service-status__title{padding:1rem 1rem .5rem;flex-shrink:0}.service-status__list{display:flex;flex-direction:column;flex:1;overflow-y:auto}.service-status__item{display:flex;align-items:center;gap:.75rem;padding:.5rem 1rem;border-bottom:1px solid var(--sapList_BorderColor, #e5e5e5)}.service-status__item:last-child{border-bottom:none}.service-status__icon{flex-shrink:0;color:var(--sapHighlightColor, #0070f2)}.service-status__name{flex:1;font-size:var(--sapFontSize, .875rem);color:var(--sapTextColor, #32363a)}.service-status__status{display:flex;align-items:center;gap:.25rem;flex-shrink:0;font-size:.75rem;font-weight:500;border-radius:.75rem;padding:.125rem .5rem}.service-status__status--operational{color:var(--sapPositiveColor, #256f3a);background:var(--sapPositiveBackground, #f1fdf6)}.service-status__status--degraded{color:var(--sapCriticalColor, #e76500);background:var(--sapCriticalBackground, #fef7f1)}.service-status__status--outage{color:var(--sapNegativeColor, #aa0808);background:var(--sapNegativeBackground, #fff1f1)}.service-status__status--maintenance{color:var(--sapNeutralColor, #5b738b);background:var(--sapNeutralBackground, #f5f6f7)}.service-status__status-icon{font-size:.75rem;width:.875rem;height:.875rem}\n"], dependencies: [{ kind: "component", type: Icon, selector: "ui5-icon, [ui5-icon]", inputs: ["design", "name", "accessibleName", "showTooltip", "mode"], outputs: ["ui5Click"], exportAs: ["ui5Icon"] }, { kind: "component", type: Title, selector: "ui5-title, [ui5-title]", inputs: ["wrappingType", "level", "size"], exportAs: ["ui5Title"] }], encapsulation: i0.ViewEncapsulation.ShadowDom });
1136
+ }
1137
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: ServiceStatusCard, decorators: [{
1138
+ type: Component,
1139
+ args: [{ selector: 'mfp-service-status-card', encapsulation: ViewEncapsulation.ShadowDom, imports: [Icon, Title], template: "<div class=\"service-status\">\n <ui5-title level=\"H5\" class=\"service-status__title\">Service Availability</ui5-title>\n <div class=\"service-status__list\">\n @for (service of services; track service.name) {\n <div class=\"service-status__item\">\n <ui5-icon name=\"{{ service.icon }}\" class=\"service-status__icon\"></ui5-icon>\n <span class=\"service-status__name\">{{ service.name }}</span>\n <div class=\"service-status__status {{ statusConfig[service.status].colorClass }}\">\n <ui5-icon name=\"{{ statusConfig[service.status].icon }}\" class=\"service-status__status-icon\"></ui5-icon>\n <span class=\"service-status__status-label\">{{ statusConfig[service.status].label }}</span>\n </div>\n </div>\n }\n </div>\n</div>\n", styles: [":host{display:block;height:100%}.service-status{display:flex;flex-direction:column;height:100%;background:var(--sapTile_Background, #fff);border-radius:var(--_ui5_card_border-radius, .5rem);border:var(--_ui5_card_border, 1px solid var(--sapTile_BorderColor, #e5e5e5));box-shadow:var(--_ui5_card_box_shadow, var(--sapContent_Shadow0));overflow:hidden}.service-status__title{padding:1rem 1rem .5rem;flex-shrink:0}.service-status__list{display:flex;flex-direction:column;flex:1;overflow-y:auto}.service-status__item{display:flex;align-items:center;gap:.75rem;padding:.5rem 1rem;border-bottom:1px solid var(--sapList_BorderColor, #e5e5e5)}.service-status__item:last-child{border-bottom:none}.service-status__icon{flex-shrink:0;color:var(--sapHighlightColor, #0070f2)}.service-status__name{flex:1;font-size:var(--sapFontSize, .875rem);color:var(--sapTextColor, #32363a)}.service-status__status{display:flex;align-items:center;gap:.25rem;flex-shrink:0;font-size:.75rem;font-weight:500;border-radius:.75rem;padding:.125rem .5rem}.service-status__status--operational{color:var(--sapPositiveColor, #256f3a);background:var(--sapPositiveBackground, #f1fdf6)}.service-status__status--degraded{color:var(--sapCriticalColor, #e76500);background:var(--sapCriticalBackground, #fef7f1)}.service-status__status--outage{color:var(--sapNegativeColor, #aa0808);background:var(--sapNegativeBackground, #fff1f1)}.service-status__status--maintenance{color:var(--sapNeutralColor, #5b738b);background:var(--sapNeutralBackground, #f5f6f7)}.service-status__status-icon{font-size:.75rem;width:.875rem;height:.875rem}\n"] }]
1140
+ }] });
1141
+
1142
+ class WhatsNew {
1143
+ headlines = [
1144
+ {
1145
+ title: 'Kubernetes 1.32 Released',
1146
+ description: 'Improved scheduling, new APIs and graduated features.',
1147
+ icon: 'cloud',
1148
+ },
1149
+ {
1150
+ title: 'Angular 20 Signals Stable',
1151
+ description: 'Signal-based reactivity is now production-ready.',
1152
+ icon: 'developer-settings',
1153
+ },
1154
+ {
1155
+ title: 'WebAssembly WASI 2.0 Preview',
1156
+ description: 'System interface advances bring server-side WASM closer.',
1157
+ icon: 'technical-object',
1158
+ },
1159
+ {
1160
+ title: 'OpenTelemetry Hits 1.0',
1161
+ description: 'Unified observability standard lands in enterprise stacks.',
1162
+ icon: 'monitor-payments',
1163
+ },
1164
+ {
1165
+ title: 'Rust Enters Linux Kernel Mainstream',
1166
+ description: 'More subsystems accept Rust driver contributions.',
1167
+ icon: 'settings',
1168
+ },
1169
+ {
1170
+ title: 'TypeScript 5.8 Performance Boost',
1171
+ description: 'Declaration emit is up to 10x faster in large projects.',
1172
+ icon: 'accelerated',
1173
+ },
1174
+ ];
1175
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: WhatsNew, deps: [], target: i0.ɵɵFactoryTarget.Component });
1176
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: WhatsNew, isStandalone: true, selector: "mfp-whats-new", ngImport: i0, template: "<div class=\"whats-new\">\n <ui5-title level=\"H5\" class=\"whats-new__title\">What's New</ui5-title>\n <ui5-list separators=\"Inner\" class=\"whats-new__list\">\n @for (item of headlines; track item.title) {\n <ui5-li icon=\"{{ item.icon }}\" description=\"{{ item.description }}\">\n {{ item.title }}\n </ui5-li>\n }\n </ui5-list>\n</div>\n", styles: [":host{display:block;height:100%}.whats-new{display:flex;flex-direction:column;height:100%;background:var(--sapTile_Background, #fff);border-radius:var(--_ui5_card_border-radius, .5rem);border:var(--_ui5_card_border, 1px solid var(--sapTile_BorderColor, #e5e5e5));box-shadow:var(--_ui5_card_box_shadow, var(--sapContent_Shadow0));overflow:hidden}.whats-new__title{padding:1rem 1rem .5rem;flex-shrink:0}.whats-new__list{flex:1;overflow-y:auto}\n"], dependencies: [{ kind: "component", type: List, selector: "ui5-list, [ui5-list]", inputs: ["headerText", "footerText", "indent", "selectionMode", "noDataText", "separators", "growing", "growingButtonText", "loading", "loadingDelay", "stickyHeader", "accessibleName", "accessibilityAttributes", "accessibleNameRef", "accessibleDescription", "accessibleDescriptionRef", "accessibleRole"], outputs: ["ui5ItemClick", "ui5ItemClose", "ui5ItemToggle", "ui5ItemDelete", "ui5SelectionChange", "ui5LoadMore", "ui5MoveOver", "ui5Move"], exportAs: ["ui5List"] }, { kind: "component", type: ListItemStandard, selector: "ui5-li, [ui5-li]", inputs: ["text", "description", "icon", "iconEnd", "additionalText", "additionalTextState", "movable", "accessibleName", "wrappingType", "type", "accessibilityAttributes", "navigated", "tooltip", "highlight", "selected"], outputs: ["ui5DetailClick"], exportAs: ["ui5ListItemStandard"] }, { kind: "component", type: Title, selector: "ui5-title, [ui5-title]", inputs: ["wrappingType", "level", "size"], exportAs: ["ui5Title"] }], encapsulation: i0.ViewEncapsulation.ShadowDom });
1177
+ }
1178
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: WhatsNew, decorators: [{
1179
+ type: Component,
1180
+ args: [{ selector: 'mfp-whats-new', encapsulation: ViewEncapsulation.ShadowDom, imports: [List, ListItemStandard, Title], template: "<div class=\"whats-new\">\n <ui5-title level=\"H5\" class=\"whats-new__title\">What's New</ui5-title>\n <ui5-list separators=\"Inner\" class=\"whats-new__list\">\n @for (item of headlines; track item.title) {\n <ui5-li icon=\"{{ item.icon }}\" description=\"{{ item.description }}\">\n {{ item.title }}\n </ui5-li>\n }\n </ui5-list>\n</div>\n", styles: [":host{display:block;height:100%}.whats-new{display:flex;flex-direction:column;height:100%;background:var(--sapTile_Background, #fff);border-radius:var(--_ui5_card_border-radius, .5rem);border:var(--_ui5_card_border, 1px solid var(--sapTile_BorderColor, #e5e5e5));box-shadow:var(--_ui5_card_box_shadow, var(--sapContent_Shadow0));overflow:hidden}.whats-new__title{padding:1rem 1rem .5rem;flex-shrink:0}.whats-new__list{flex:1;overflow-y:auto}\n"] }]
1181
+ }] });
1182
+
1183
+ /**
1184
+ * Generated bundle index. Do not edit.
1185
+ */
1186
+
1187
+ export { AddCardDialog, CARD_TYPES, Dashboard, DashboardCard, DashboardSection, DeclarativeForm, DeclarativeTable, DeclarativeTableCard, Favorites, ServiceStatusCard, VisitedServiceCard, WhatsNew };
1188
+ //# sourceMappingURL=openmfp-ngx.mjs.map