@fundamental-ngx/platform 0.65.1-rc.2 → 0.65.1-rc.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fesm2022/fundamental-ngx-platform-approval-flow.mjs +3 -3
- package/fesm2022/fundamental-ngx-platform-approval-flow.mjs.map +1 -1
- package/fesm2022/fundamental-ngx-platform-form.mjs +3 -3
- package/fesm2022/fundamental-ngx-platform-form.mjs.map +1 -1
- package/fesm2022/fundamental-ngx-platform-message-popover.mjs +29 -683
- package/fesm2022/fundamental-ngx-platform-message-popover.mjs.map +1 -1
- package/fesm2022/fundamental-ngx-platform-message-view.mjs +170 -0
- package/fesm2022/fundamental-ngx-platform-message-view.mjs.map +1 -0
- package/fesm2022/fundamental-ngx-platform-messages-shared.mjs +803 -0
- package/fesm2022/fundamental-ngx-platform-messages-shared.mjs.map +1 -0
- package/fesm2022/fundamental-ngx-platform-search-field.mjs +1 -1
- package/fesm2022/fundamental-ngx-platform-search-field.mjs.map +1 -1
- package/fesm2022/fundamental-ngx-platform-smart-filter-bar.mjs +2 -2
- package/fesm2022/fundamental-ngx-platform-smart-filter-bar.mjs.map +1 -1
- package/fesm2022/fundamental-ngx-platform-table.mjs +5 -5
- package/fesm2022/fundamental-ngx-platform-table.mjs.map +1 -1
- package/fesm2022/fundamental-ngx-platform-value-help-dialog.mjs +1 -1
- package/fesm2022/fundamental-ngx-platform-value-help-dialog.mjs.map +1 -1
- package/fesm2022/fundamental-ngx-platform-variant-management.mjs +2 -2
- package/fesm2022/fundamental-ngx-platform-variant-management.mjs.map +1 -1
- package/fesm2022/fundamental-ngx-platform-wizard-generator.mjs +1 -1
- package/fesm2022/fundamental-ngx-platform-wizard-generator.mjs.map +1 -1
- package/fesm2022/fundamental-ngx-platform.mjs +1 -0
- package/fesm2022/fundamental-ngx-platform.mjs.map +1 -1
- package/message-view/README.md +88 -0
- package/package.json +12 -4
- package/schematics/ng-add/index.js +1 -1
- package/schematics/package.json +1 -1
- package/types/fundamental-ngx-platform-message-popover.d.ts +9 -253
- package/types/fundamental-ngx-platform-message-view.d.ts +70 -0
- package/types/fundamental-ngx-platform-messages-shared.d.ts +306 -0
- package/types/fundamental-ngx-platform.d.ts +1 -0
|
@@ -0,0 +1,803 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { InjectionToken, Input, Optional, Directive, signal, ContentChildren, Inject, ViewEncapsulation, ChangeDetectionStrategy, Component, EventEmitter, inject, DOCUMENT, ElementRef, ViewChild, Output, computed, NgModule } from '@angular/core';
|
|
3
|
+
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
4
|
+
import * as i1 from '@angular/forms';
|
|
5
|
+
import { FormGroup, ControlContainer } from '@angular/forms';
|
|
6
|
+
import { FD_FORM_FIELD_CONTROL } from '@fundamental-ngx/cdk/forms';
|
|
7
|
+
import { Subject, Subscription, startWith, switchMap, filter, zip, debounceTime } from 'rxjs';
|
|
8
|
+
import { CdkScrollable } from '@angular/cdk/overlay';
|
|
9
|
+
import { NgTemplateOutlet } from '@angular/common';
|
|
10
|
+
import * as i1$1 from '@fundamental-ngx/cdk/utils';
|
|
11
|
+
import { resizeObservable, ClickedDirective, TabbableElementService, countBy, mergeWith } from '@fundamental-ngx/cdk/utils';
|
|
12
|
+
import { LinkComponent } from '@fundamental-ngx/core/link';
|
|
13
|
+
import * as i2 from '@fundamental-ngx/core/list';
|
|
14
|
+
import { ListModule } from '@fundamental-ngx/core/list';
|
|
15
|
+
import { ObjectStatusComponent } from '@fundamental-ngx/core/object-status';
|
|
16
|
+
import { ScrollbarDirective } from '@fundamental-ngx/core/scrollbar';
|
|
17
|
+
import { TextComponent } from '@fundamental-ngx/core/text';
|
|
18
|
+
import { FdTranslatePipe } from '@fundamental-ngx/i18n';
|
|
19
|
+
import { getFormState } from '@fundamental-ngx/platform/form';
|
|
20
|
+
|
|
21
|
+
const FDP_MESSAGE_POPOVER_DEFAULT_CONFIG = {
|
|
22
|
+
errors: {
|
|
23
|
+
email: 'platformMessagePopover.defaultErrors.email',
|
|
24
|
+
max: 'platformMessagePopover.defaultErrors.max',
|
|
25
|
+
maxlength: 'platformMessagePopover.defaultErrors.maxLength',
|
|
26
|
+
min: 'platformMessagePopover.defaultErrors.min',
|
|
27
|
+
minlength: 'platformMessagePopover.defaultErrors.minLength',
|
|
28
|
+
pattern: 'platformMessagePopover.defaultErrors.pattern',
|
|
29
|
+
required: 'platformMessagePopover.defaultErrors.required',
|
|
30
|
+
requiredTrue: 'platformMessagePopover.defaultErrors.requiredTrue'
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
const FDP_MESSAGE_POPOVER_CONFIG = new InjectionToken('FdpMessagePopoverConfig');
|
|
34
|
+
|
|
35
|
+
class MessagePopoverFormItemDirective {
|
|
36
|
+
/** @hidden */
|
|
37
|
+
constructor(elementRef, control) {
|
|
38
|
+
this.elementRef = elementRef;
|
|
39
|
+
this.control = control;
|
|
40
|
+
}
|
|
41
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.2.1", ngImport: i0, type: MessagePopoverFormItemDirective, deps: [{ token: i0.ElementRef }, { token: i1.NgControl, optional: true }], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
42
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.2.1", type: MessagePopoverFormItemDirective, isStandalone: true, selector: "[fdpMessagePopoverFormItem]", inputs: { label: ["fdpMessagePopoverFormItem", "label"], errorTypes: "errorTypes" }, ngImport: i0 }); }
|
|
43
|
+
}
|
|
44
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.2.1", ngImport: i0, type: MessagePopoverFormItemDirective, decorators: [{
|
|
45
|
+
type: Directive,
|
|
46
|
+
args: [{
|
|
47
|
+
selector: '[fdpMessagePopoverFormItem]'
|
|
48
|
+
}]
|
|
49
|
+
}], ctorParameters: () => [{ type: i0.ElementRef }, { type: i1.NgControl, decorators: [{
|
|
50
|
+
type: Optional
|
|
51
|
+
}] }], propDecorators: { label: [{
|
|
52
|
+
type: Input,
|
|
53
|
+
args: ['fdpMessagePopoverFormItem']
|
|
54
|
+
}], errorTypes: [{
|
|
55
|
+
type: Input
|
|
56
|
+
}] } });
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Converts Object Status into Message Popover State
|
|
60
|
+
* @param status Object status
|
|
61
|
+
* @returns Message Popover State.
|
|
62
|
+
*/
|
|
63
|
+
function convertFormStateToMessagePopoverState(state) {
|
|
64
|
+
switch (state) {
|
|
65
|
+
case 'error':
|
|
66
|
+
return 'negative';
|
|
67
|
+
case 'warning':
|
|
68
|
+
return 'critical';
|
|
69
|
+
case 'default':
|
|
70
|
+
return 'neutral';
|
|
71
|
+
default:
|
|
72
|
+
return state;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Converts Form State into Object status type.
|
|
77
|
+
* @param type Form state.
|
|
78
|
+
* @returns `ObjectStatus` type.
|
|
79
|
+
*/
|
|
80
|
+
function convertFormState(type) {
|
|
81
|
+
switch (type) {
|
|
82
|
+
case 'success':
|
|
83
|
+
return 'positive';
|
|
84
|
+
case 'error':
|
|
85
|
+
return 'negative';
|
|
86
|
+
case 'warning':
|
|
87
|
+
return 'critical';
|
|
88
|
+
case 'information':
|
|
89
|
+
return 'informative';
|
|
90
|
+
default:
|
|
91
|
+
return 'neutral';
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Maps message type to icon name.
|
|
96
|
+
* @param type Message type (error, success, warning, information, default).
|
|
97
|
+
* @returns Icon name for the given message type.
|
|
98
|
+
*/
|
|
99
|
+
function getIconForMessageType(type) {
|
|
100
|
+
switch (type) {
|
|
101
|
+
case 'error':
|
|
102
|
+
return 'error';
|
|
103
|
+
case 'success':
|
|
104
|
+
return 'sys-enter-2';
|
|
105
|
+
case 'warning':
|
|
106
|
+
return 'alert';
|
|
107
|
+
case 'information':
|
|
108
|
+
return 'information';
|
|
109
|
+
default:
|
|
110
|
+
return 'sys-help-2';
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
class MessagePopoverFormWrapperComponent {
|
|
115
|
+
/**
|
|
116
|
+
* User-passed forms.
|
|
117
|
+
*/
|
|
118
|
+
set forms(forms) {
|
|
119
|
+
if (!forms) {
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
if (!Array.isArray(forms)) {
|
|
123
|
+
forms = [forms];
|
|
124
|
+
}
|
|
125
|
+
this._ngForms = forms;
|
|
126
|
+
this._startListeningForErrors();
|
|
127
|
+
}
|
|
128
|
+
/** User-passed form fields. */
|
|
129
|
+
set formFields(value) {
|
|
130
|
+
this._formFields = value;
|
|
131
|
+
this._listenToFormFieldErrors(value);
|
|
132
|
+
}
|
|
133
|
+
get formFields() {
|
|
134
|
+
return this._formFields;
|
|
135
|
+
}
|
|
136
|
+
/** @hidden */
|
|
137
|
+
constructor(_destroyRef, _config) {
|
|
138
|
+
this._destroyRef = _destroyRef;
|
|
139
|
+
this._config = _config;
|
|
140
|
+
/** Message Popover instance. */
|
|
141
|
+
this.messagePopover$ = new Subject();
|
|
142
|
+
/**
|
|
143
|
+
* Error models Signal. Emitted when form submitted and contains invalid fields.
|
|
144
|
+
*/
|
|
145
|
+
this.errors$ = signal([], /* @ts-ignore */
|
|
146
|
+
...(ngDevMode ? [{ debugName: "errors$" }] : /* istanbul ignore next */ []));
|
|
147
|
+
/** @hidden */
|
|
148
|
+
this._formFields = [];
|
|
149
|
+
/** @hidden */
|
|
150
|
+
this._ngForms = [];
|
|
151
|
+
/** @hidden */
|
|
152
|
+
this._formItemErrorsSubscription = new Subscription();
|
|
153
|
+
/** @hidden */
|
|
154
|
+
this._formSubmitted = false;
|
|
155
|
+
}
|
|
156
|
+
/** @hidden */
|
|
157
|
+
ngAfterViewInit() {
|
|
158
|
+
// Forms are passed via input property.
|
|
159
|
+
if (this._ngForms.length > 0) {
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
this._projectedForm?.changes.pipe(startWith(null), takeUntilDestroyed(this._destroyRef)).subscribe(() => {
|
|
163
|
+
this._ngForms = this._projectedForm.toArray();
|
|
164
|
+
this._startListeningForErrors();
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Sets Message Popover component instance.
|
|
169
|
+
*/
|
|
170
|
+
setMessagePopover(messagePopover) {
|
|
171
|
+
this.messagePopover$.next(messagePopover);
|
|
172
|
+
// this.messagePopover = messagePopover;
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Programmatically add new form to array of forms.
|
|
176
|
+
* @param forms
|
|
177
|
+
*/
|
|
178
|
+
addForms(forms) {
|
|
179
|
+
const formsArray = Array.isArray(forms) ? forms : [forms];
|
|
180
|
+
this._ngForms.push(...formsArray);
|
|
181
|
+
this._startListeningForErrors();
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Programmatically add new form fields to array of listened form fields.
|
|
185
|
+
* @param formFields
|
|
186
|
+
*/
|
|
187
|
+
addFormFields(formFields) {
|
|
188
|
+
this._formFields.push(...formFields);
|
|
189
|
+
this._listenToFormFieldErrors(this._formFields);
|
|
190
|
+
}
|
|
191
|
+
/** @hidden */
|
|
192
|
+
ngOnDestroy() {
|
|
193
|
+
this._formItemErrorsSubscription.unsubscribe();
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* @hidden
|
|
197
|
+
* Listens to the form submission and collects form control errors.
|
|
198
|
+
*/
|
|
199
|
+
_startListeningForErrors() {
|
|
200
|
+
if (!this._ngForms) {
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
this._formErrorsSubscription?.unsubscribe();
|
|
204
|
+
this.errors$.set([]);
|
|
205
|
+
const formSubmitEvents = this._ngForms.map((form) => form.ngSubmit.pipe(switchMap(() => form.statusChanges.pipe(startWith(form.status), filter((status) => status.toLowerCase() !== 'pending')))));
|
|
206
|
+
this._formErrorsSubscription = zip(...formSubmitEvents)
|
|
207
|
+
.pipe(takeUntilDestroyed(this._destroyRef))
|
|
208
|
+
.subscribe(() => {
|
|
209
|
+
this._formSubmitted = true;
|
|
210
|
+
const errors = this._directiveItems.length > 0 ? this._collectPlainFormData() : this._collectAdvancedFormData();
|
|
211
|
+
this.errors$.set(errors);
|
|
212
|
+
});
|
|
213
|
+
this._listenToFormFieldErrors(this._projectedFormFieldControls.toArray());
|
|
214
|
+
this._projectedFormFieldControls?.changes.subscribe(() => {
|
|
215
|
+
this._listenToFormFieldErrors(this._projectedFormFieldControls.toArray());
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
/** @hidden */
|
|
219
|
+
_listenToFormFieldErrors(fields) {
|
|
220
|
+
this._formItemErrorsSubscription.unsubscribe();
|
|
221
|
+
this._formItemErrorsSubscription = new Subscription();
|
|
222
|
+
fields?.forEach((field) => {
|
|
223
|
+
this._formItemErrorsSubscription.add(field.formField?.errorsChange$.subscribe(() => {
|
|
224
|
+
if (!this._formSubmitted) {
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
const errors = this._directiveItems.length > 0
|
|
228
|
+
? this._collectPlainFormData()
|
|
229
|
+
: this._collectAdvancedFormData();
|
|
230
|
+
this.errors$.set(errors);
|
|
231
|
+
}));
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
/** @hidden */
|
|
235
|
+
_collectPlainFormData() {
|
|
236
|
+
const errors = [
|
|
237
|
+
{
|
|
238
|
+
errors: []
|
|
239
|
+
}
|
|
240
|
+
];
|
|
241
|
+
const fields = this._directiveItems.toArray();
|
|
242
|
+
this._ngForms.forEach((form) => {
|
|
243
|
+
Object.keys(form.form.controls).forEach((controlName) => {
|
|
244
|
+
const control = form.form.get(controlName);
|
|
245
|
+
const field = fields.find((formField) => formField.control?.name === controlName);
|
|
246
|
+
if (!control?.errors || !field) {
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
const configErrors = Object.keys(this._config.errors);
|
|
250
|
+
Object.keys(control.errors)
|
|
251
|
+
.filter((error) => configErrors.includes(error))
|
|
252
|
+
.forEach((errorKey) => {
|
|
253
|
+
const errorObj = control.errors[errorKey];
|
|
254
|
+
const errorTextModel = this._getConfigErrorModel(errorKey, field.errorTypes);
|
|
255
|
+
const errorConfig = field?.errorTypes ? field?.errorTypes[errorKey] : null;
|
|
256
|
+
const errorState = errorConfig && this._isAdvancedError(errorConfig) && errorConfig.type
|
|
257
|
+
? errorConfig.type
|
|
258
|
+
: 'error';
|
|
259
|
+
const error = {
|
|
260
|
+
name: controlName,
|
|
261
|
+
type: errorTextModel.type,
|
|
262
|
+
state: convertFormState(errorState),
|
|
263
|
+
fieldName: field?.label ?? '',
|
|
264
|
+
element: field?.elementRef,
|
|
265
|
+
heading: {
|
|
266
|
+
type: 'string',
|
|
267
|
+
error: errorObj,
|
|
268
|
+
message: errorTextModel.heading
|
|
269
|
+
},
|
|
270
|
+
description: {
|
|
271
|
+
type: 'string',
|
|
272
|
+
error: errorObj,
|
|
273
|
+
message: errorTextModel.description
|
|
274
|
+
},
|
|
275
|
+
errors: control.errors
|
|
276
|
+
};
|
|
277
|
+
errors[0].errors.push(error);
|
|
278
|
+
});
|
|
279
|
+
});
|
|
280
|
+
});
|
|
281
|
+
return errors;
|
|
282
|
+
}
|
|
283
|
+
/** @hidden */
|
|
284
|
+
_collectAdvancedFormData() {
|
|
285
|
+
let errors = [];
|
|
286
|
+
const fields = this.formFields.length > 0 ? this.formFields : this._projectedFormFieldControls.toArray();
|
|
287
|
+
/**
|
|
288
|
+
* Iterates over form controls to collect their errors.
|
|
289
|
+
* @param form Form Group or a collection of Form Groups.
|
|
290
|
+
*/
|
|
291
|
+
const iterateOverForm = (form) => {
|
|
292
|
+
if (Array.isArray(form)) {
|
|
293
|
+
form.forEach((formGroup) => {
|
|
294
|
+
errors = iterateOverForm(formGroup);
|
|
295
|
+
});
|
|
296
|
+
return errors;
|
|
297
|
+
}
|
|
298
|
+
Object.keys(form.controls).forEach((controlName) => {
|
|
299
|
+
const control = form.get(controlName);
|
|
300
|
+
const field = fields.find((formField) => formField.id === controlName);
|
|
301
|
+
if (control instanceof FormGroup) {
|
|
302
|
+
errors = iterateOverForm(control);
|
|
303
|
+
}
|
|
304
|
+
if (!control?.errors || !field) {
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
Object.keys(control.errors).forEach((errorKey) => {
|
|
308
|
+
const errorDirective = field.formField?.groupedErrors.find((groupedError) => groupedError.directive.error === errorKey);
|
|
309
|
+
const groupName = this._getGroupName(field?.formField);
|
|
310
|
+
const error = this._getErrorModel(controlName, groupName, control, field, errorKey, errorDirective, control.errors[errorKey]);
|
|
311
|
+
let errorGroupIndex = errors.findIndex((errorGroup) => errorGroup.group === groupName);
|
|
312
|
+
if (errorGroupIndex === -1) {
|
|
313
|
+
errorGroupIndex =
|
|
314
|
+
errors.push({
|
|
315
|
+
group: groupName,
|
|
316
|
+
errors: []
|
|
317
|
+
}) - 1;
|
|
318
|
+
}
|
|
319
|
+
const group = errors[errorGroupIndex];
|
|
320
|
+
group.errors.push(error);
|
|
321
|
+
});
|
|
322
|
+
});
|
|
323
|
+
return errors;
|
|
324
|
+
};
|
|
325
|
+
return iterateOverForm(this._ngForms.map((form) => form.form));
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* @hidden
|
|
329
|
+
* Creates error model
|
|
330
|
+
*/
|
|
331
|
+
_getErrorModel(controlName, groupName, control, field, errorKey, errorDirective, error) {
|
|
332
|
+
let state = 'error';
|
|
333
|
+
const configError = this._config.errors[errorKey];
|
|
334
|
+
if (errorDirective?.directive.type) {
|
|
335
|
+
state = errorDirective.directive.type;
|
|
336
|
+
}
|
|
337
|
+
else if (this._isAdvancedError(configError)) {
|
|
338
|
+
state = configError.type;
|
|
339
|
+
}
|
|
340
|
+
return {
|
|
341
|
+
name: controlName,
|
|
342
|
+
type: state,
|
|
343
|
+
state: convertFormState(state),
|
|
344
|
+
group: groupName,
|
|
345
|
+
fieldName: field?.formField?.label ?? '',
|
|
346
|
+
heading: this._getErrorText(field, errorDirective, 'heading', errorKey, error),
|
|
347
|
+
description: this._getErrorText(field, errorDirective, 'description', errorKey, error),
|
|
348
|
+
errors: control.errors,
|
|
349
|
+
formField: field,
|
|
350
|
+
element: field?.elementRef
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* @hidden
|
|
355
|
+
* Creates error text based on the type of the error.
|
|
356
|
+
* @param field Form Field Control.
|
|
357
|
+
* @param errorDirective Optional Error directive
|
|
358
|
+
* @param section Section where text should be rendered.
|
|
359
|
+
* @param errorKey
|
|
360
|
+
* @param error
|
|
361
|
+
*/
|
|
362
|
+
_getErrorText(field, errorDirective, section = 'heading', errorKey, error) {
|
|
363
|
+
const errorTextObj = {
|
|
364
|
+
error: errorDirective?.error,
|
|
365
|
+
type: errorDirective?.directive ? 'directive' : field?.formField?.i18Strings ? 'templateRef' : 'string'
|
|
366
|
+
};
|
|
367
|
+
if (errorTextObj.type === 'string') {
|
|
368
|
+
const errorTextModel = this._getConfigErrorModel(errorKey);
|
|
369
|
+
errorTextObj.error = error;
|
|
370
|
+
errorTextObj.message = errorTextModel[section];
|
|
371
|
+
return errorTextObj;
|
|
372
|
+
}
|
|
373
|
+
if (errorDirective?.directive) {
|
|
374
|
+
errorTextObj.message =
|
|
375
|
+
section === 'heading'
|
|
376
|
+
? (errorDirective.directive._headingTemplateRef ?? errorDirective.directive._descriptionTemplateRef)
|
|
377
|
+
: errorDirective.directive._descriptionTemplateRef;
|
|
378
|
+
return errorTextObj;
|
|
379
|
+
}
|
|
380
|
+
errorTextObj.message = field?.formField?.i18Strings ?? null;
|
|
381
|
+
return errorTextObj;
|
|
382
|
+
}
|
|
383
|
+
/** @hidden */
|
|
384
|
+
_getGroupName(formField) {
|
|
385
|
+
const parts = [];
|
|
386
|
+
if (formField?.formGroupContainer?.mainTitle) {
|
|
387
|
+
parts.push(formField.formGroupContainer.mainTitle);
|
|
388
|
+
}
|
|
389
|
+
if (formField?.formFieldGroup?.label) {
|
|
390
|
+
parts.push(formField.formFieldGroup.label);
|
|
391
|
+
}
|
|
392
|
+
return parts.join(', ');
|
|
393
|
+
}
|
|
394
|
+
/** @hidden */
|
|
395
|
+
_getConfigErrorModel(errorKey, customErrorTypes) {
|
|
396
|
+
const configError = customErrorTypes && customErrorTypes[errorKey] ? customErrorTypes[errorKey] : this._config.errors[errorKey];
|
|
397
|
+
const isPlainError = !this._isAdvancedError(configError);
|
|
398
|
+
const errorType = isPlainError ? 'error' : configError.type;
|
|
399
|
+
const headingMessage = isPlainError ? configError : configError.heading;
|
|
400
|
+
const descriptionMessage = isPlainError ? undefined : configError.description;
|
|
401
|
+
return {
|
|
402
|
+
type: errorType,
|
|
403
|
+
heading: headingMessage,
|
|
404
|
+
description: descriptionMessage
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
/** @hidden */
|
|
408
|
+
_isAdvancedError(error) {
|
|
409
|
+
return !!error && typeof error !== 'string';
|
|
410
|
+
}
|
|
411
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.2.1", ngImport: i0, type: MessagePopoverFormWrapperComponent, deps: [{ token: i0.DestroyRef }, { token: FDP_MESSAGE_POPOVER_CONFIG }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
412
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.2.1", type: MessagePopoverFormWrapperComponent, isStandalone: true, selector: "fdp-message-popover-form-wrapper", inputs: { forms: "forms", formFields: "formFields" }, queries: [{ propertyName: "_projectedForm", predicate: ControlContainer, descendants: true }, { propertyName: "_projectedFormFieldControls", predicate: FD_FORM_FIELD_CONTROL, descendants: true }, { propertyName: "_directiveItems", predicate: MessagePopoverFormItemDirective, descendants: true }], exportAs: ["messagePopoverWrapper"], ngImport: i0, template: `<ng-content></ng-content>`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
|
|
413
|
+
}
|
|
414
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.2.1", ngImport: i0, type: MessagePopoverFormWrapperComponent, decorators: [{
|
|
415
|
+
type: Component,
|
|
416
|
+
args: [{
|
|
417
|
+
selector: 'fdp-message-popover-form-wrapper',
|
|
418
|
+
template: `<ng-content></ng-content>`,
|
|
419
|
+
exportAs: 'messagePopoverWrapper',
|
|
420
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
421
|
+
encapsulation: ViewEncapsulation.None
|
|
422
|
+
}]
|
|
423
|
+
}], ctorParameters: () => [{ type: i0.DestroyRef }, { type: undefined, decorators: [{
|
|
424
|
+
type: Inject,
|
|
425
|
+
args: [FDP_MESSAGE_POPOVER_CONFIG]
|
|
426
|
+
}] }], propDecorators: { _projectedForm: [{
|
|
427
|
+
type: ContentChildren,
|
|
428
|
+
args: [ControlContainer, { descendants: true }]
|
|
429
|
+
}], _projectedFormFieldControls: [{
|
|
430
|
+
type: ContentChildren,
|
|
431
|
+
args: [FD_FORM_FIELD_CONTROL, { descendants: true }]
|
|
432
|
+
}], _directiveItems: [{
|
|
433
|
+
type: ContentChildren,
|
|
434
|
+
args: [MessagePopoverFormItemDirective, { descendants: true }]
|
|
435
|
+
}], forms: [{
|
|
436
|
+
type: Input
|
|
437
|
+
}], formFields: [{
|
|
438
|
+
type: Input
|
|
439
|
+
}] } });
|
|
440
|
+
|
|
441
|
+
const ANIMATION_EASING = 'cubic-bezier(0, 0, 0.2, 1)';
|
|
442
|
+
const ANIMATION_DURATION = 100;
|
|
443
|
+
class MessagesListComponent {
|
|
444
|
+
/** Current Message Popover screen. Can be either `list` or `details`. */
|
|
445
|
+
set currentScreen(value) {
|
|
446
|
+
const prev = this._currentScreen;
|
|
447
|
+
this._currentScreen = value;
|
|
448
|
+
if (prev && prev !== value) {
|
|
449
|
+
this._animateScreenTransition(value);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
get currentScreen() {
|
|
453
|
+
return this._currentScreen;
|
|
454
|
+
}
|
|
455
|
+
/** @hidden */
|
|
456
|
+
constructor(_destroyRef, _tabbableService, _document) {
|
|
457
|
+
this._destroyRef = _destroyRef;
|
|
458
|
+
this._tabbableService = _tabbableService;
|
|
459
|
+
this._document = _document;
|
|
460
|
+
/** Event emitted when user clicks on error entry containing additional description. */
|
|
461
|
+
this.openDetails = new EventEmitter();
|
|
462
|
+
/** Event emitted when user clicks on error entry and item's element should be focused. */
|
|
463
|
+
this.focusItem = new EventEmitter();
|
|
464
|
+
/** Event emitted when user clicks on the field link to focus on the field itself. */
|
|
465
|
+
this.closePopover = new EventEmitter();
|
|
466
|
+
/** @hidden */
|
|
467
|
+
this._currentScreen = 'list';
|
|
468
|
+
/** @hidden */
|
|
469
|
+
this._activeListElement = null;
|
|
470
|
+
/** @hidden */
|
|
471
|
+
this._listAnimation = null;
|
|
472
|
+
/** @hidden */
|
|
473
|
+
this._detailsAnimation = null;
|
|
474
|
+
/** @hidden */
|
|
475
|
+
this._translatePipe = inject(FdTranslatePipe);
|
|
476
|
+
/** @hidden */
|
|
477
|
+
this._additionalInfoText = this._translatePipe.transform('platformMessageView.additionalInformation');
|
|
478
|
+
this._destroyRef.onDestroy(() => {
|
|
479
|
+
this._listAnimation?.cancel();
|
|
480
|
+
this._listAnimation = null;
|
|
481
|
+
this._detailsAnimation?.cancel();
|
|
482
|
+
this._detailsAnimation = null;
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
/** @hidden */
|
|
486
|
+
ngAfterViewInit() {
|
|
487
|
+
resizeObservable(this.detailsView.nativeElement)
|
|
488
|
+
.pipe(debounceTime(20), takeUntilDestroyed(this._destroyRef))
|
|
489
|
+
.subscribe(() => {
|
|
490
|
+
const { height } = this.detailsView.nativeElement.getBoundingClientRect();
|
|
491
|
+
this.listSection.nativeElement.style.minHeight = `${height}px`;
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
/** @hidden */
|
|
495
|
+
showDetails(entry) {
|
|
496
|
+
this._activeListElement = this._document.activeElement;
|
|
497
|
+
if (!entry.description.message) {
|
|
498
|
+
this._focusElement(undefined, entry);
|
|
499
|
+
return;
|
|
500
|
+
}
|
|
501
|
+
this.currentScreen = 'details';
|
|
502
|
+
this.openDetails.emit(entry);
|
|
503
|
+
}
|
|
504
|
+
/** @hidden */
|
|
505
|
+
_focusElement(event, item) {
|
|
506
|
+
if (!item?.element?.nativeElement) {
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
event?.stopImmediatePropagation();
|
|
510
|
+
this.closePopover.emit(false);
|
|
511
|
+
this.focusItem.emit(item);
|
|
512
|
+
setTimeout(() => {
|
|
513
|
+
const tabbableElement = this._tabbableService.getTabbableElement(item.element?.nativeElement);
|
|
514
|
+
tabbableElement?.focus();
|
|
515
|
+
});
|
|
516
|
+
}
|
|
517
|
+
/** @hidden Map message type to icon name */
|
|
518
|
+
getIconForType(type) {
|
|
519
|
+
return getIconForMessageType(type);
|
|
520
|
+
}
|
|
521
|
+
/** @hidden Get heading text for fd-text component */
|
|
522
|
+
getHeadingText(item) {
|
|
523
|
+
if (item.heading.type === 'string' && typeof item.heading.message === 'string') {
|
|
524
|
+
return item.heading.message;
|
|
525
|
+
}
|
|
526
|
+
return '';
|
|
527
|
+
}
|
|
528
|
+
/** @hidden Get announcement text for screen reader when navigating to details */
|
|
529
|
+
getAnnouncementText(entry) {
|
|
530
|
+
let headingText = '';
|
|
531
|
+
if (entry.heading.type === 'string' && entry.heading.message) {
|
|
532
|
+
headingText = typeof entry.heading.message === 'string' ? entry.heading.message : '';
|
|
533
|
+
}
|
|
534
|
+
const additionalInfoText = this._additionalInfoText();
|
|
535
|
+
return headingText ? `${headingText}. ${additionalInfoText}` : '';
|
|
536
|
+
}
|
|
537
|
+
/** @hidden Animate the transition between list and details screens. */
|
|
538
|
+
_animateScreenTransition(screen) {
|
|
539
|
+
const listEl = this.listSection?.nativeElement;
|
|
540
|
+
const detailsEl = this.detailsSection?.nativeElement;
|
|
541
|
+
if (!listEl || !detailsEl) {
|
|
542
|
+
return;
|
|
543
|
+
}
|
|
544
|
+
this._listAnimation?.cancel();
|
|
545
|
+
this._detailsAnimation?.cancel();
|
|
546
|
+
if (screen === 'details') {
|
|
547
|
+
this._animateListToDetails(listEl, detailsEl);
|
|
548
|
+
}
|
|
549
|
+
else {
|
|
550
|
+
this._animateDetailsToList(listEl, detailsEl);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
/** @hidden Slide list out left, details in from right. */
|
|
554
|
+
_animateListToDetails(listEl, detailsEl) {
|
|
555
|
+
if (typeof listEl.animate !== 'function') {
|
|
556
|
+
this._focusDetailsView();
|
|
557
|
+
return;
|
|
558
|
+
}
|
|
559
|
+
this._listAnimation = listEl.animate([
|
|
560
|
+
{ transform: 'translateX(0)', opacity: 1 },
|
|
561
|
+
{ transform: 'translateX(-50px)', opacity: 0 }
|
|
562
|
+
], { duration: ANIMATION_DURATION, easing: ANIMATION_EASING, fill: 'forwards' });
|
|
563
|
+
this._detailsAnimation = detailsEl.animate([
|
|
564
|
+
{ transform: 'translateX(50px)', opacity: 0 },
|
|
565
|
+
{ transform: 'translateX(0)', opacity: 1 }
|
|
566
|
+
], { duration: ANIMATION_DURATION, easing: ANIMATION_EASING, fill: 'forwards' });
|
|
567
|
+
this._detailsAnimation.finished
|
|
568
|
+
.then(() => {
|
|
569
|
+
this._detailsAnimation = null;
|
|
570
|
+
this._focusDetailsView();
|
|
571
|
+
})
|
|
572
|
+
.catch(() => {
|
|
573
|
+
this._detailsAnimation = null;
|
|
574
|
+
});
|
|
575
|
+
}
|
|
576
|
+
/** @hidden Slide details out right, list in from left. */
|
|
577
|
+
_animateDetailsToList(listEl, detailsEl) {
|
|
578
|
+
if (typeof listEl.animate !== 'function') {
|
|
579
|
+
return;
|
|
580
|
+
}
|
|
581
|
+
this._detailsAnimation = detailsEl.animate([
|
|
582
|
+
{ transform: 'translateX(0)', opacity: 1 },
|
|
583
|
+
{ transform: 'translateX(50px)', opacity: 0 }
|
|
584
|
+
], { duration: ANIMATION_DURATION, easing: ANIMATION_EASING, fill: 'forwards' });
|
|
585
|
+
this._listAnimation = listEl.animate([
|
|
586
|
+
{ transform: 'translateX(-50px)', opacity: 0 },
|
|
587
|
+
{ transform: 'translateX(0)', opacity: 1 }
|
|
588
|
+
], { duration: ANIMATION_DURATION, easing: ANIMATION_EASING, fill: 'forwards' });
|
|
589
|
+
this._listAnimation.finished
|
|
590
|
+
.then(() => {
|
|
591
|
+
this._listAnimation = null;
|
|
592
|
+
if (this._activeListElement) {
|
|
593
|
+
this._activeListElement.focus();
|
|
594
|
+
this._activeListElement = null;
|
|
595
|
+
}
|
|
596
|
+
})
|
|
597
|
+
.catch(() => {
|
|
598
|
+
this._listAnimation = null;
|
|
599
|
+
});
|
|
600
|
+
}
|
|
601
|
+
/** @hidden Focus the first interactive element in the details view. */
|
|
602
|
+
_focusDetailsView() {
|
|
603
|
+
if (!this.detailsView?.nativeElement) {
|
|
604
|
+
return;
|
|
605
|
+
}
|
|
606
|
+
// Try to find the first tabbable element in the details view
|
|
607
|
+
const firstTabbable = this._tabbableService.getTabbableElement(this.detailsView.nativeElement);
|
|
608
|
+
if (firstTabbable) {
|
|
609
|
+
firstTabbable.focus();
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.2.1", ngImport: i0, type: MessagesListComponent, deps: [{ token: i0.DestroyRef }, { token: i1$1.TabbableElementService }, { token: DOCUMENT }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
613
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.2.1", type: MessagesListComponent, isStandalone: true, selector: "fdp-messages-list", inputs: { currentScreen: "currentScreen", filteredErrors: "filteredErrors", currentEntry: "currentEntry" }, outputs: { openDetails: "openDetails", focusItem: "focusItem", closePopover: "closePopover" }, host: { classAttribute: "fd-message-popover__view-container" }, providers: [TabbableElementService, FdTranslatePipe], viewQueries: [{ propertyName: "listView", first: true, predicate: ["listView"], descendants: true, read: ElementRef }, { propertyName: "detailsView", first: true, predicate: ["detailsView"], descendants: true, read: ElementRef }, { propertyName: "listSection", first: true, predicate: ["listSection"], descendants: true, read: ElementRef }, { propertyName: "detailsSection", first: true, predicate: ["detailsSection"], descendants: true, read: ElementRef }], ngImport: i0, template: "<ng-template #headingTemplate let-item>\n <ng-template #directiveHeading>\n <ng-template\n [ngTemplateOutlet]=\"item.heading.message || item.description.message\"\n [ngTemplateOutletContext]=\"{ $implicit: item.heading.error }\"\n ></ng-template>\n </ng-template>\n <ng-template #i18nHeading>\n <ng-template\n [ngTemplateOutlet]=\"item.heading.message\"\n [ngTemplateOutletContext]=\"{ $implicit: item.errors }\"\n ></ng-template>\n </ng-template>\n <ng-template #stringHeading>\n {{ (item.heading.message | fdTranslate: { error: item.heading.error } : item.heading.message)() }}\n </ng-template>\n <ng-template\n [ngTemplateOutlet]=\"\n item.heading.type === 'directive'\n ? directiveHeading\n : item.heading.type === 'string'\n ? stringHeading\n : i18nHeading\n \"\n ></ng-template>\n</ng-template>\n<ng-template #descriptionTemplate let-item>\n <ng-template #directiveDescription>\n <ng-template\n [ngTemplateOutlet]=\"item.description.message!\"\n [ngTemplateOutletContext]=\"{ $implicit: item.description.error }\"\n ></ng-template>\n </ng-template>\n <ng-template #i18nDescription>\n <ng-template\n [ngTemplateOutlet]=\"item.description.message!\"\n [ngTemplateOutletContext]=\"{ $implicit: item.errors }\"\n ></ng-template>\n </ng-template>\n <ng-template #stringDescription>\n {{ (item.description.message | fdTranslate: { error: item.description.error } : item.description.message)() }}\n </ng-template>\n <ng-template\n [ngTemplateOutlet]=\"\n item.description.type === 'directive'\n ? directiveDescription\n : item.description.type === 'string'\n ? stringDescription\n : i18nDescription\n \"\n ></ng-template>\n</ng-template>\n<div class=\"fd-message-view\">\n <!-- Screen reader announcement region for details view navigation -->\n <div aria-live=\"assertive\" aria-atomic=\"true\" class=\"fd-message-view__sr-only\">\n @if (currentScreen === 'details' && currentEntry) {\n {{ getAnnouncementText(currentEntry) }}\n }\n </div>\n <section\n #listSection\n class=\"fd-message-view__list\"\n [class.fd-message-view__list--hidden]=\"currentScreen === 'details'\"\n [attr.inert]=\"currentScreen === 'details' ? '' : null\"\n [attr.aria-hidden]=\"currentScreen === 'details'\"\n tabindex=\"-1\"\n fd-scrollbar\n >\n <ul #listView fd-list class=\"message-popover__list fd-list--message-view\" [navigationIndicator]=\"true\">\n @for (group of filteredErrors; track group) {\n @if (group.group) {\n <li fd-list-group-header>\n <span fd-list-title>{{ group.group }}</span>\n </li>\n }\n @for (item of group.errors; track item; let i = $index) {\n <li\n fd-list-item\n [tabindex]=\"i === 0 ? 0 : -1\"\n [byline]=\"true\"\n [counter]=\"item.aggregatedMessages!\"\n (fdkClicked)=\"showDetails(item)\"\n >\n <ng-template #itemIcon>\n <span\n fd-object-status\n class=\"fd-list__icon\"\n [status]=\"item.state\"\n [glyph]=\"getIconForType(item.type)\"\n ></span>\n </ng-template>\n <ng-template #listSubtitle>\n @if (item.heading.type === 'string' && item.heading.message) {\n <fd-text\n [text]=\"getHeadingText(item)\"\n [maxLines]=\"3\"\n [expandable]=\"true\"\n (fdkClicked)=\"$event.stopPropagation()\"\n ></fd-text>\n } @else {\n <span fd-list-subline>\n <ng-template\n [ngTemplateOutlet]=\"headingTemplate\"\n [ngTemplateOutletContext]=\"{ $implicit: item }\"\n ></ng-template>\n </span>\n }\n </ng-template>\n @if (item.element && !!item.description.message) {\n <span fd-list-link [navigationIndicator]=\"!!item.description.message\">\n <ng-template [ngTemplateOutlet]=\"itemIcon\"></ng-template>\n <span fd-list-content>\n <span fd-list-title>\n <a tabindex=\"0\" fd-link (fdkClicked)=\"_focusElement($event, item)\">\n {{ item.fieldName }}\n </a>\n </span>\n <ng-template [ngTemplateOutlet]=\"listSubtitle\"></ng-template>\n </span>\n </span>\n } @else {\n <a fd-list-link [navigationIndicator]=\"!!item.description.message\">\n <ng-template [ngTemplateOutlet]=\"itemIcon\"></ng-template>\n <span fd-list-content>\n <span fd-list-title>{{ item.fieldName }}</span>\n <ng-template [ngTemplateOutlet]=\"listSubtitle\"></ng-template>\n </span>\n </a>\n }\n </li>\n }\n }\n </ul>\n </section>\n <section\n #detailsSection\n class=\"fd-message-view__details\"\n [class.fd-message-view__details--visible]=\"currentScreen === 'details'\"\n [attr.inert]=\"currentScreen === 'list' ? '' : null\"\n [attr.aria-hidden]=\"currentScreen === 'list'\"\n >\n <div #detailsView>\n @if (currentEntry) {\n <span class=\"fd-message-view__details-title\">\n <span\n fd-object-status\n class=\"fd-message-view__icon\"\n [status]=\"currentEntry.state\"\n [glyph]=\"getIconForType(currentEntry.type)\"\n ></span>\n <ng-template\n [ngTemplateOutlet]=\"headingTemplate\"\n [ngTemplateOutletContext]=\"{ $implicit: currentEntry }\"\n ></ng-template>\n </span>\n @if (currentEntry.subtitle) {\n <span class=\"fd-message-view__details-subtitle\">\n {{ currentEntry.subtitle }}\n </span>\n }\n @if (currentEntry.description.message) {\n <span class=\"fd-message-view__details-description\">\n <ng-template\n [ngTemplateOutlet]=\"descriptionTemplate\"\n [ngTemplateOutletContext]=\"{ $implicit: currentEntry }\"\n ></ng-template>\n </span>\n }\n @if (currentEntry.link) {\n <span class=\"fd-message-view__details-link\">\n @if (currentEntry.link.href) {\n <a\n fd-link\n [attr.href]=\"currentEntry.link.href\"\n [attr.target]=\"currentEntry.link.target || '_blank'\"\n >\n {{ currentEntry.link.text }}\n </a>\n } @else {\n <a fd-link tabindex=\"0\" (fdkClicked)=\"currentEntry.link.callback?.()\">\n {{ currentEntry.link.text }}\n </a>\n }\n </span>\n }\n }\n </div>\n </section>\n</div>\n", styles: [".fd-message-view__sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0}.fd-message-view__details-subtitle{display:block;color:var(--sapContent_LabelColor);margin-block-start:.25rem;margin-block-end:.5rem}.message-popover__list.fd-list--navigation-indication .fd-list__link--navigation-indicator:after{min-width:3rem}.fd-list__content fd-text{display:block;width:100%}.fd-list__content fd-text .fd-text{white-space:normal;margin:0;padding:0;line-height:normal}.fd-list__content fd-text .fd-text__lineclamp{display:block;line-height:normal}.fd-list__content fd-text .fd-text__link--more{pointer-events:auto}.fd-list__content fd-text .fd-text__link--more:hover{text-decoration:underline}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: ScrollbarDirective, selector: "[fdScrollbar], [fd-scrollbar]", inputs: ["noHorizontalScroll", "noVerticalScroll", "alwaysVisible"] }, { kind: "ngmodule", type: ListModule }, { kind: "component", type: i2.ListComponent, selector: "[fd-list], [fdList]", inputs: ["dropdownMode", "multiInputMode", "mobileMode", "hasMessage", "noBorder", "navigationIndicator", "selection", "keyboardSupport", "byline", "subline", "theme", "unreadIndicator", "role", "settingsList", "settingsListFooter", "notification", "searchResultsList"], outputs: ["focusEscapeList"] }, { kind: "component", type: i2.ListItemComponent, selector: "[fdListItem] ,[fd-list-item]", inputs: ["selected", "noData", "action", "interactive", "growing", "counter", "counterRole", "counterAriaLabel", "active", "unread", "byline", "ariaRole", "id", "preventClick", "settingsListTpl", "suggestion"], outputs: ["keyDown"], exportAs: ["fdListItem"] }, { kind: "directive", type: i2.ListTitleDirective, selector: "[fd-list-title], [fdListTitle]", inputs: ["wrap", "truncate", "scope"] }, { kind: "directive", type: i2.ListGroupHeaderDirective, selector: "[fdListGroupHeader], [fd-list-group-header]", inputs: ["nativeElementId"], outputs: ["keyDown"] }, { kind: "directive", type: i2.ListLinkDirective, selector: "[fd-list-link], [fdListLink]", inputs: ["navigationIndicator", "navigated", "focusable"] }, { kind: "directive", type: i2.ListContentDirective, selector: "[fdListContent], [fd-list-content]", inputs: ["twoCol"] }, { kind: "directive", type: i2.ListSublineDirective, selector: "[fdListSubline], [fd-list-subline]", inputs: ["truncate"] }, { kind: "component", type: ObjectStatusComponent, selector: "[fd-object-status]", inputs: ["status", "statusMessage", "glyph", "glyphFont", "label", "ariaLabel", "glyphAriaLabel", "indicationColor", "clickable", "inverted", "large", "secondaryIndication", "textTemplate", "ariaRoleDescription"] }, { kind: "component", type: LinkComponent, selector: "[fdLink], [fd-link]", inputs: ["emphasized", "disabled", "inverted", "subtle", "undecorated", "touchTarget"] }, { kind: "directive", type: ClickedDirective, selector: "[fdkClicked]", outputs: ["fdkClicked"] }, { kind: "component", type: TextComponent, selector: "fd-text", inputs: ["text", "maxLines", "whitespaces", "hyphenation", "expandable", "isCollapsed", "subline"], outputs: ["isCollapsedChange"] }, { kind: "pipe", type: FdTranslatePipe, name: "fdTranslate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
|
|
614
|
+
}
|
|
615
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.2.1", ngImport: i0, type: MessagesListComponent, decorators: [{
|
|
616
|
+
type: Component,
|
|
617
|
+
args: [{ selector: 'fdp-messages-list', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, providers: [TabbableElementService, FdTranslatePipe], host: {
|
|
618
|
+
class: 'fd-message-popover__view-container'
|
|
619
|
+
}, imports: [
|
|
620
|
+
NgTemplateOutlet,
|
|
621
|
+
CdkScrollable,
|
|
622
|
+
ScrollbarDirective,
|
|
623
|
+
ListModule,
|
|
624
|
+
ObjectStatusComponent,
|
|
625
|
+
LinkComponent,
|
|
626
|
+
FdTranslatePipe,
|
|
627
|
+
ClickedDirective,
|
|
628
|
+
TextComponent
|
|
629
|
+
], template: "<ng-template #headingTemplate let-item>\n <ng-template #directiveHeading>\n <ng-template\n [ngTemplateOutlet]=\"item.heading.message || item.description.message\"\n [ngTemplateOutletContext]=\"{ $implicit: item.heading.error }\"\n ></ng-template>\n </ng-template>\n <ng-template #i18nHeading>\n <ng-template\n [ngTemplateOutlet]=\"item.heading.message\"\n [ngTemplateOutletContext]=\"{ $implicit: item.errors }\"\n ></ng-template>\n </ng-template>\n <ng-template #stringHeading>\n {{ (item.heading.message | fdTranslate: { error: item.heading.error } : item.heading.message)() }}\n </ng-template>\n <ng-template\n [ngTemplateOutlet]=\"\n item.heading.type === 'directive'\n ? directiveHeading\n : item.heading.type === 'string'\n ? stringHeading\n : i18nHeading\n \"\n ></ng-template>\n</ng-template>\n<ng-template #descriptionTemplate let-item>\n <ng-template #directiveDescription>\n <ng-template\n [ngTemplateOutlet]=\"item.description.message!\"\n [ngTemplateOutletContext]=\"{ $implicit: item.description.error }\"\n ></ng-template>\n </ng-template>\n <ng-template #i18nDescription>\n <ng-template\n [ngTemplateOutlet]=\"item.description.message!\"\n [ngTemplateOutletContext]=\"{ $implicit: item.errors }\"\n ></ng-template>\n </ng-template>\n <ng-template #stringDescription>\n {{ (item.description.message | fdTranslate: { error: item.description.error } : item.description.message)() }}\n </ng-template>\n <ng-template\n [ngTemplateOutlet]=\"\n item.description.type === 'directive'\n ? directiveDescription\n : item.description.type === 'string'\n ? stringDescription\n : i18nDescription\n \"\n ></ng-template>\n</ng-template>\n<div class=\"fd-message-view\">\n <!-- Screen reader announcement region for details view navigation -->\n <div aria-live=\"assertive\" aria-atomic=\"true\" class=\"fd-message-view__sr-only\">\n @if (currentScreen === 'details' && currentEntry) {\n {{ getAnnouncementText(currentEntry) }}\n }\n </div>\n <section\n #listSection\n class=\"fd-message-view__list\"\n [class.fd-message-view__list--hidden]=\"currentScreen === 'details'\"\n [attr.inert]=\"currentScreen === 'details' ? '' : null\"\n [attr.aria-hidden]=\"currentScreen === 'details'\"\n tabindex=\"-1\"\n fd-scrollbar\n >\n <ul #listView fd-list class=\"message-popover__list fd-list--message-view\" [navigationIndicator]=\"true\">\n @for (group of filteredErrors; track group) {\n @if (group.group) {\n <li fd-list-group-header>\n <span fd-list-title>{{ group.group }}</span>\n </li>\n }\n @for (item of group.errors; track item; let i = $index) {\n <li\n fd-list-item\n [tabindex]=\"i === 0 ? 0 : -1\"\n [byline]=\"true\"\n [counter]=\"item.aggregatedMessages!\"\n (fdkClicked)=\"showDetails(item)\"\n >\n <ng-template #itemIcon>\n <span\n fd-object-status\n class=\"fd-list__icon\"\n [status]=\"item.state\"\n [glyph]=\"getIconForType(item.type)\"\n ></span>\n </ng-template>\n <ng-template #listSubtitle>\n @if (item.heading.type === 'string' && item.heading.message) {\n <fd-text\n [text]=\"getHeadingText(item)\"\n [maxLines]=\"3\"\n [expandable]=\"true\"\n (fdkClicked)=\"$event.stopPropagation()\"\n ></fd-text>\n } @else {\n <span fd-list-subline>\n <ng-template\n [ngTemplateOutlet]=\"headingTemplate\"\n [ngTemplateOutletContext]=\"{ $implicit: item }\"\n ></ng-template>\n </span>\n }\n </ng-template>\n @if (item.element && !!item.description.message) {\n <span fd-list-link [navigationIndicator]=\"!!item.description.message\">\n <ng-template [ngTemplateOutlet]=\"itemIcon\"></ng-template>\n <span fd-list-content>\n <span fd-list-title>\n <a tabindex=\"0\" fd-link (fdkClicked)=\"_focusElement($event, item)\">\n {{ item.fieldName }}\n </a>\n </span>\n <ng-template [ngTemplateOutlet]=\"listSubtitle\"></ng-template>\n </span>\n </span>\n } @else {\n <a fd-list-link [navigationIndicator]=\"!!item.description.message\">\n <ng-template [ngTemplateOutlet]=\"itemIcon\"></ng-template>\n <span fd-list-content>\n <span fd-list-title>{{ item.fieldName }}</span>\n <ng-template [ngTemplateOutlet]=\"listSubtitle\"></ng-template>\n </span>\n </a>\n }\n </li>\n }\n }\n </ul>\n </section>\n <section\n #detailsSection\n class=\"fd-message-view__details\"\n [class.fd-message-view__details--visible]=\"currentScreen === 'details'\"\n [attr.inert]=\"currentScreen === 'list' ? '' : null\"\n [attr.aria-hidden]=\"currentScreen === 'list'\"\n >\n <div #detailsView>\n @if (currentEntry) {\n <span class=\"fd-message-view__details-title\">\n <span\n fd-object-status\n class=\"fd-message-view__icon\"\n [status]=\"currentEntry.state\"\n [glyph]=\"getIconForType(currentEntry.type)\"\n ></span>\n <ng-template\n [ngTemplateOutlet]=\"headingTemplate\"\n [ngTemplateOutletContext]=\"{ $implicit: currentEntry }\"\n ></ng-template>\n </span>\n @if (currentEntry.subtitle) {\n <span class=\"fd-message-view__details-subtitle\">\n {{ currentEntry.subtitle }}\n </span>\n }\n @if (currentEntry.description.message) {\n <span class=\"fd-message-view__details-description\">\n <ng-template\n [ngTemplateOutlet]=\"descriptionTemplate\"\n [ngTemplateOutletContext]=\"{ $implicit: currentEntry }\"\n ></ng-template>\n </span>\n }\n @if (currentEntry.link) {\n <span class=\"fd-message-view__details-link\">\n @if (currentEntry.link.href) {\n <a\n fd-link\n [attr.href]=\"currentEntry.link.href\"\n [attr.target]=\"currentEntry.link.target || '_blank'\"\n >\n {{ currentEntry.link.text }}\n </a>\n } @else {\n <a fd-link tabindex=\"0\" (fdkClicked)=\"currentEntry.link.callback?.()\">\n {{ currentEntry.link.text }}\n </a>\n }\n </span>\n }\n }\n </div>\n </section>\n</div>\n", styles: [".fd-message-view__sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0}.fd-message-view__details-subtitle{display:block;color:var(--sapContent_LabelColor);margin-block-start:.25rem;margin-block-end:.5rem}.message-popover__list.fd-list--navigation-indication .fd-list__link--navigation-indicator:after{min-width:3rem}.fd-list__content fd-text{display:block;width:100%}.fd-list__content fd-text .fd-text{white-space:normal;margin:0;padding:0;line-height:normal}.fd-list__content fd-text .fd-text__lineclamp{display:block;line-height:normal}.fd-list__content fd-text .fd-text__link--more{pointer-events:auto}.fd-list__content fd-text .fd-text__link--more:hover{text-decoration:underline}\n"] }]
|
|
630
|
+
}], ctorParameters: () => [{ type: i0.DestroyRef }, { type: i1$1.TabbableElementService }, { type: Document, decorators: [{
|
|
631
|
+
type: Inject,
|
|
632
|
+
args: [DOCUMENT]
|
|
633
|
+
}] }], propDecorators: { currentScreen: [{
|
|
634
|
+
type: Input
|
|
635
|
+
}], filteredErrors: [{
|
|
636
|
+
type: Input
|
|
637
|
+
}], currentEntry: [{
|
|
638
|
+
type: Input
|
|
639
|
+
}], openDetails: [{
|
|
640
|
+
type: Output
|
|
641
|
+
}], focusItem: [{
|
|
642
|
+
type: Output
|
|
643
|
+
}], closePopover: [{
|
|
644
|
+
type: Output
|
|
645
|
+
}], listView: [{
|
|
646
|
+
type: ViewChild,
|
|
647
|
+
args: ['listView', { read: ElementRef }]
|
|
648
|
+
}], detailsView: [{
|
|
649
|
+
type: ViewChild,
|
|
650
|
+
args: ['detailsView', { read: ElementRef }]
|
|
651
|
+
}], listSection: [{
|
|
652
|
+
type: ViewChild,
|
|
653
|
+
args: ['listSection', { read: ElementRef }]
|
|
654
|
+
}], detailsSection: [{
|
|
655
|
+
type: ViewChild,
|
|
656
|
+
args: ['detailsSection', { read: ElementRef }]
|
|
657
|
+
}] } });
|
|
658
|
+
|
|
659
|
+
/**
|
|
660
|
+
* Abstract base class for message list components (MessagePopover and MessageView).
|
|
661
|
+
* Contains shared logic for managing error states, filtering, and navigation between list/detail views.
|
|
662
|
+
*/
|
|
663
|
+
class MessageListShared {
|
|
664
|
+
constructor() {
|
|
665
|
+
/** Event emits when user clicks on error entry and item's element needs to be focused. */
|
|
666
|
+
this.focusItem = new EventEmitter();
|
|
667
|
+
/** Current message popover screen. Can be `list` or `details`. */
|
|
668
|
+
this.currentScreen = 'list';
|
|
669
|
+
/** Current error entry. */
|
|
670
|
+
this.currentEntry = null;
|
|
671
|
+
/** @hidden */
|
|
672
|
+
this._currentErrorType = signal('all', /* @ts-ignore */
|
|
673
|
+
...(ngDevMode ? [{ debugName: "_currentErrorType" }] : /* istanbul ignore next */ []));
|
|
674
|
+
/** @hidden */
|
|
675
|
+
this._errorTypes$ = computed(() => {
|
|
676
|
+
const countedErrors = this.countedErrors$();
|
|
677
|
+
const errorTypes = Object.keys(countedErrors);
|
|
678
|
+
return errorTypes.map((errorType) => ({
|
|
679
|
+
group: errorType,
|
|
680
|
+
count: countedErrors[errorType],
|
|
681
|
+
state: convertFormState(errorType)
|
|
682
|
+
}));
|
|
683
|
+
}, /* @ts-ignore */
|
|
684
|
+
...(ngDevMode ? [{ debugName: "_errorTypes$" }] : /* istanbul ignore next */ []));
|
|
685
|
+
/** @hidden */
|
|
686
|
+
this._priorityStateItemsCount$ = computed(() => this.countedErrors$()[this._priorityFormState$()] || 0, /* @ts-ignore */
|
|
687
|
+
...(ngDevMode ? [{ debugName: "_priorityStateItemsCount$" }] : /* istanbul ignore next */ []));
|
|
688
|
+
/** @hidden */
|
|
689
|
+
this._priorityFormState$ = computed(() => {
|
|
690
|
+
const countedErrors = this.countedErrors$();
|
|
691
|
+
const errorTypes = Object.keys(countedErrors);
|
|
692
|
+
return getFormState(errorTypes);
|
|
693
|
+
}, /* @ts-ignore */
|
|
694
|
+
...(ngDevMode ? [{ debugName: "_priorityFormState$" }] : /* istanbul ignore next */ []));
|
|
695
|
+
/** @hidden */
|
|
696
|
+
this._priorityState$ = computed(() => convertFormStateToMessagePopoverState(this._priorityFormState$()), /* @ts-ignore */
|
|
697
|
+
...(ngDevMode ? [{ debugName: "_priorityState$" }] : /* istanbul ignore next */ []));
|
|
698
|
+
/** @hidden */
|
|
699
|
+
this._filteredErrors$ = computed(() => {
|
|
700
|
+
const groupedErrors = this.groupedErrors$();
|
|
701
|
+
const errorType = this._currentErrorType();
|
|
702
|
+
if (errorType === 'all') {
|
|
703
|
+
return groupedErrors;
|
|
704
|
+
}
|
|
705
|
+
const filteredErrors = [];
|
|
706
|
+
groupedErrors.forEach((group) => {
|
|
707
|
+
const errors = group.errors.filter((error) => error.type === errorType);
|
|
708
|
+
if (errors.length === 0) {
|
|
709
|
+
return;
|
|
710
|
+
}
|
|
711
|
+
filteredErrors.push({
|
|
712
|
+
group: group.group,
|
|
713
|
+
errors
|
|
714
|
+
});
|
|
715
|
+
});
|
|
716
|
+
return filteredErrors;
|
|
717
|
+
}, /* @ts-ignore */
|
|
718
|
+
...(ngDevMode ? [{ debugName: "_filteredErrors$" }] : /* istanbul ignore next */ []));
|
|
719
|
+
/** @hidden */
|
|
720
|
+
this.groupedErrors$ = computed(() => this.wrapper$()?.errors$() || [], /* @ts-ignore */
|
|
721
|
+
...(ngDevMode ? [{ debugName: "groupedErrors$" }] : /* istanbul ignore next */ []));
|
|
722
|
+
/** @hidden */
|
|
723
|
+
this.wrapper$ = signal(null, /* @ts-ignore */
|
|
724
|
+
...(ngDevMode ? [{ debugName: "wrapper$" }] : /* istanbul ignore next */ []));
|
|
725
|
+
/** @hidden */
|
|
726
|
+
this.countedErrors$ = computed(() => {
|
|
727
|
+
const allErrors = this.groupedErrors$().flatMap((group) => group.errors);
|
|
728
|
+
return countBy(allErrors, 'type');
|
|
729
|
+
}, /* @ts-ignore */
|
|
730
|
+
...(ngDevMode ? [{ debugName: "countedErrors$" }] : /* istanbul ignore next */ []));
|
|
731
|
+
}
|
|
732
|
+
/** @hidden */
|
|
733
|
+
showList() {
|
|
734
|
+
this.currentScreen = 'list';
|
|
735
|
+
this.currentEntry = null;
|
|
736
|
+
}
|
|
737
|
+
/** @hidden */
|
|
738
|
+
showDetails(entry) {
|
|
739
|
+
this.currentScreen = 'details';
|
|
740
|
+
this.currentEntry = entry;
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
/**
|
|
745
|
+
* @deprecated
|
|
746
|
+
* Use direct imports of components and directives.
|
|
747
|
+
*/
|
|
748
|
+
class PlatformMessagesSharedModule {
|
|
749
|
+
/**
|
|
750
|
+
* Method allows users to apply custom configuration.
|
|
751
|
+
* @param config Object containing error definitions.
|
|
752
|
+
*/
|
|
753
|
+
static withConfig(config) {
|
|
754
|
+
const customConfig = mergeWith(FDP_MESSAGE_POPOVER_DEFAULT_CONFIG, config, (obj, src) => {
|
|
755
|
+
if (typeof obj === 'object' && !Array.isArray(obj)) {
|
|
756
|
+
return Object.assign(obj, src);
|
|
757
|
+
}
|
|
758
|
+
else if (Array.isArray(obj)) {
|
|
759
|
+
return obj.concat(src);
|
|
760
|
+
}
|
|
761
|
+
else {
|
|
762
|
+
return src;
|
|
763
|
+
}
|
|
764
|
+
});
|
|
765
|
+
return {
|
|
766
|
+
ngModule: PlatformMessagesSharedModule,
|
|
767
|
+
providers: [
|
|
768
|
+
{
|
|
769
|
+
provide: FDP_MESSAGE_POPOVER_CONFIG,
|
|
770
|
+
useValue: customConfig
|
|
771
|
+
}
|
|
772
|
+
]
|
|
773
|
+
};
|
|
774
|
+
}
|
|
775
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.2.1", ngImport: i0, type: PlatformMessagesSharedModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
|
|
776
|
+
static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "22.2.1", ngImport: i0, type: PlatformMessagesSharedModule, imports: [MessagePopoverFormWrapperComponent, MessagesListComponent, MessagePopoverFormItemDirective], exports: [MessagePopoverFormWrapperComponent, MessagesListComponent, MessagePopoverFormItemDirective] }); }
|
|
777
|
+
static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "22.2.1", ngImport: i0, type: PlatformMessagesSharedModule, providers: [
|
|
778
|
+
{
|
|
779
|
+
provide: FDP_MESSAGE_POPOVER_CONFIG,
|
|
780
|
+
useValue: FDP_MESSAGE_POPOVER_DEFAULT_CONFIG
|
|
781
|
+
}
|
|
782
|
+
], imports: [MessagesListComponent] }); }
|
|
783
|
+
}
|
|
784
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.2.1", ngImport: i0, type: PlatformMessagesSharedModule, decorators: [{
|
|
785
|
+
type: NgModule,
|
|
786
|
+
args: [{
|
|
787
|
+
imports: [MessagePopoverFormWrapperComponent, MessagesListComponent, MessagePopoverFormItemDirective],
|
|
788
|
+
exports: [MessagePopoverFormWrapperComponent, MessagesListComponent, MessagePopoverFormItemDirective],
|
|
789
|
+
providers: [
|
|
790
|
+
{
|
|
791
|
+
provide: FDP_MESSAGE_POPOVER_CONFIG,
|
|
792
|
+
useValue: FDP_MESSAGE_POPOVER_DEFAULT_CONFIG
|
|
793
|
+
}
|
|
794
|
+
]
|
|
795
|
+
}]
|
|
796
|
+
}] });
|
|
797
|
+
|
|
798
|
+
/**
|
|
799
|
+
* Generated bundle index. Do not edit.
|
|
800
|
+
*/
|
|
801
|
+
|
|
802
|
+
export { FDP_MESSAGE_POPOVER_CONFIG, FDP_MESSAGE_POPOVER_DEFAULT_CONFIG, MessageListShared, MessagePopoverFormItemDirective, MessagePopoverFormWrapperComponent, MessagesListComponent, PlatformMessagesSharedModule, convertFormState, convertFormStateToMessagePopoverState, getIconForMessageType };
|
|
803
|
+
//# sourceMappingURL=fundamental-ngx-platform-messages-shared.mjs.map
|