@pega/angular-sdk-overrides 0.26.1 → 0.26.3

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.
Files changed (133) hide show
  1. package/lib/designSystemExtension/alert/alert.component.html +4 -1
  2. package/lib/designSystemExtension/alert/alert.component.scss +9 -10
  3. package/lib/designSystemExtension/alert/alert.component.ts +6 -2
  4. package/lib/designSystemExtension/field-group/field-group.component.ts +11 -3
  5. package/lib/designSystemExtension/material-case-summary/material-case-summary.component.html +12 -3
  6. package/lib/designSystemExtension/operator/operator.component.ts +2 -1
  7. package/lib/designSystemExtension/rich-text-editor/rich-text-editor.component.html +104 -10
  8. package/lib/designSystemExtension/rich-text-editor/rich-text-editor.component.scss +141 -1
  9. package/lib/designSystemExtension/rich-text-editor/rich-text-editor.component.ts +224 -72
  10. package/lib/field/auto-complete/auto-complete.component.html +46 -5
  11. package/lib/field/auto-complete/auto-complete.component.scss +92 -0
  12. package/lib/field/auto-complete/auto-complete.component.ts +349 -22
  13. package/lib/field/cancel-alert/cancel-alert.component.ts +38 -15
  14. package/lib/field/check-box/check-box.component.html +3 -1
  15. package/lib/field/check-box/check-box.component.ts +10 -1
  16. package/lib/field/currency/currency.component.html +2 -1
  17. package/lib/field/currency/currency.component.ts +10 -1
  18. package/lib/field/date/date.component.html +2 -1
  19. package/lib/field/date/date.component.ts +2 -0
  20. package/lib/field/date-time/date-time.component.html +2 -1
  21. package/lib/field/date-time/date-time.component.scss +46 -40
  22. package/lib/field/date-time/date-time.component.ts +2 -0
  23. package/lib/field/decimal/decimal.component.html +2 -1
  24. package/lib/field/decimal/decimal.component.ts +2 -0
  25. package/lib/field/dropdown/dropdown.component.html +2 -1
  26. package/lib/field/dropdown/dropdown.component.ts +10 -1
  27. package/lib/field/email/email.component.html +2 -1
  28. package/lib/field/email/email.component.ts +2 -2
  29. package/lib/field/field.base.ts +27 -1
  30. package/lib/field/group/group.component.html +1 -1
  31. package/lib/field/group/group.component.ts +1 -3
  32. package/lib/field/integer/integer.component.html +2 -1
  33. package/lib/field/integer/integer.component.ts +4 -2
  34. package/lib/field/location/location.component.html +2 -1
  35. package/lib/field/location/location.component.ts +3 -1
  36. package/lib/field/object-reference/object-reference.component.html +1 -1
  37. package/lib/field/object-reference/object-reference.component.ts +3 -0
  38. package/lib/field/percentage/percentage.component.html +2 -1
  39. package/lib/field/percentage/percentage.component.ts +10 -1
  40. package/lib/field/phone/phone.component.html +2 -1
  41. package/lib/field/phone/phone.component.ts +2 -1
  42. package/lib/field/radio-buttons/radio-buttons.component.html +2 -1
  43. package/lib/field/radio-buttons/radio-buttons.component.ts +10 -1
  44. package/lib/field/rich-text/rich-text.component.scss +1 -166
  45. package/lib/field/rich-text/rich-text.component.ts +1 -1
  46. package/lib/field/semantic-link/semantic-link.component.html +1 -1
  47. package/lib/field/semantic-link/semantic-link.component.scss +4 -0
  48. package/lib/field/semantic-link/semantic-link.component.ts +9 -5
  49. package/lib/field/text-area/text-area.component.html +2 -1
  50. package/lib/field/text-area/text-area.component.ts +2 -1
  51. package/lib/field/text-input/text-input.component.html +2 -1
  52. package/lib/field/text-input/text-input.component.ts +2 -1
  53. package/lib/field/time/time.component.html +2 -1
  54. package/lib/field/time/time.component.ts +2 -1
  55. package/lib/field/url/url.component.html +2 -1
  56. package/lib/field/url/url.component.ts +2 -1
  57. package/lib/field/user-reference/user-reference.component.html +4 -2
  58. package/lib/field/user-reference/user-reference.component.ts +10 -5
  59. package/lib/infra/Containers/flow-container/flow-container.component.html +1 -1
  60. package/lib/infra/Containers/flow-container/flow-container.component.ts +33 -40
  61. package/lib/infra/Containers/modal-view-container/data-view-action-buttons/data-view-action-buttons.component.html +12 -0
  62. package/lib/infra/Containers/modal-view-container/data-view-action-buttons/data-view-action-buttons.component.spec.ts +130 -0
  63. package/lib/infra/Containers/modal-view-container/data-view-action-buttons/data-view-action-buttons.component.ts +77 -0
  64. package/lib/infra/Containers/modal-view-container/modal-view-container.component.html +35 -8
  65. package/lib/infra/Containers/modal-view-container/modal-view-container.component.spec.ts +24 -11
  66. package/lib/infra/Containers/modal-view-container/modal-view-container.component.ts +185 -194
  67. package/lib/infra/Containers/view-container/view-container.component.ts +32 -31
  68. package/lib/infra/assignment/assignment.component.html +2 -1
  69. package/lib/infra/assignment/assignment.component.ts +30 -13
  70. package/lib/infra/defer-load/defer-load.component.ts +6 -3
  71. package/lib/infra/multi-step/multi-step.component.html +25 -33
  72. package/lib/infra/multi-step/multi-step.component.scss +48 -0
  73. package/lib/infra/multi-step/multi-step.component.ts +11 -0
  74. package/lib/infra/navbar/navbar.component.ts +33 -35
  75. package/lib/infra/root-container/root-container.component.ts +33 -38
  76. package/lib/infra/view/view.component.ts +17 -39
  77. package/lib/template/app-shell/app-shell.component.ts +12 -11
  78. package/lib/template/base/details-template-base.ts +35 -53
  79. package/lib/template/case-summary/case-summary.component.ts +15 -1
  80. package/lib/template/case-view/case-view.component.html +13 -1
  81. package/lib/template/case-view/case-view.component.spec.ts +119 -10
  82. package/lib/template/case-view/case-view.component.ts +52 -3
  83. package/lib/template/data-reference/data-reference.component.ts +27 -18
  84. package/lib/template/data-reference/search-form/search-form.component.html +1 -1
  85. package/lib/template/data-reference/search-form/search-form.component.ts +2 -0
  86. package/lib/template/default-form/default-form.component.html +19 -3
  87. package/lib/template/default-form/default-form.component.ts +13 -3
  88. package/lib/template/details/details.component.html +23 -3
  89. package/lib/template/details/details.component.scss +9 -2
  90. package/lib/template/details/details.component.ts +3 -32
  91. package/lib/template/details-narrow-wide/details-narrow-wide.component.html +21 -2
  92. package/lib/template/details-narrow-wide/details-narrow-wide.component.scss +8 -1
  93. package/lib/template/details-narrow-wide/details-narrow-wide.component.ts +3 -37
  94. package/lib/template/details-one-column/details-one-column.component.html +23 -1
  95. package/lib/template/details-one-column/details-one-column.component.scss +14 -0
  96. package/lib/template/details-one-column/details-one-column.component.ts +3 -38
  97. package/lib/template/details-three-column/details-three-column.component.html +21 -2
  98. package/lib/template/details-three-column/details-three-column.component.scss +8 -1
  99. package/lib/template/details-three-column/details-three-column.component.ts +3 -46
  100. package/lib/template/details-two-column/details-two-column.component.html +21 -2
  101. package/lib/template/details-two-column/details-two-column.component.scss +8 -1
  102. package/lib/template/details-two-column/details-two-column.component.ts +3 -44
  103. package/lib/template/details-wide-narrow/details-wide-narrow.component.html +21 -2
  104. package/lib/template/details-wide-narrow/details-wide-narrow.component.scss +8 -1
  105. package/lib/template/details-wide-narrow/details-wide-narrow.component.ts +3 -41
  106. package/lib/template/field-value-list/field-value-list.component.scss +17 -0
  107. package/lib/template/list-view/list-view.component.ts +39 -0
  108. package/lib/template/self-service-case-view/self-service-case-view.component.html +13 -1
  109. package/lib/template/self-service-case-view/self-service-case-view.component.spec.ts +96 -11
  110. package/lib/template/self-service-case-view/self-service-case-view.component.ts +52 -3
  111. package/lib/template/simple-table-manual/helpers.ts +30 -9
  112. package/lib/template/simple-table-manual/simple-table-manual.component.html +30 -14
  113. package/lib/template/simple-table-manual/simple-table-manual.component.scss +4 -0
  114. package/lib/template/simple-table-manual/simple-table-manual.component.ts +118 -29
  115. package/lib/template/single-reference-readonly/single-reference-readonly.component.html +1 -1
  116. package/lib/template/single-reference-readonly/single-reference-readonly.component.scss +2 -2
  117. package/lib/template/single-reference-readonly/single-reference-readonly.component.ts +4 -4
  118. package/lib/template/wss-nav-bar/wss-nav-bar.component.ts +18 -20
  119. package/lib/widget/attachment/AttachmentUtils.ts +18 -10
  120. package/lib/widget/attachment/attachment.component.html +16 -6
  121. package/lib/widget/attachment/attachment.component.ts +12 -2
  122. package/lib/widget/case-history/case-history.component.ts +6 -2
  123. package/lib/widget/file-utility/file-utility.component.ts +25 -29
  124. package/lib/widget/todo/todo.component.ts +7 -2
  125. package/package.json +1 -1
  126. package/lib/designSystemExtension/material-details/material-details.component.html +0 -40
  127. package/lib/designSystemExtension/material-details/material-details.component.scss +0 -116
  128. package/lib/designSystemExtension/material-details/material-details.component.spec.ts +0 -24
  129. package/lib/designSystemExtension/material-details/material-details.component.ts +0 -31
  130. package/lib/designSystemExtension/material-details-fields/material-details-fields.component.html +0 -41
  131. package/lib/designSystemExtension/material-details-fields/material-details-fields.component.scss +0 -34
  132. package/lib/designSystemExtension/material-details-fields/material-details-fields.component.spec.ts +0 -22
  133. package/lib/designSystemExtension/material-details-fields/material-details-fields.component.ts +0 -61
@@ -1,18 +1,37 @@
1
- import { Component, Input, Output, EventEmitter, OnChanges } from '@angular/core';
1
+ import {
2
+ AfterViewInit,
3
+ ChangeDetectorRef,
4
+ Component,
5
+ ElementRef,
6
+ EventEmitter,
7
+ Input,
8
+ OnChanges,
9
+ OnDestroy,
10
+ Output,
11
+ SimpleChanges,
12
+ ViewChild
13
+ } from '@angular/core';
2
14
  import { CommonModule } from '@angular/common';
3
15
  import { FormControl, ReactiveFormsModule, Validators } from '@angular/forms';
4
- import { EditorModule, TINYMCE_SCRIPT_SRC } from '@tinymce/tinymce-angular';
16
+ import { MatButtonModule } from '@angular/material/button';
17
+ import { MatFormFieldModule } from '@angular/material/form-field';
18
+ import { MatIconModule } from '@angular/material/icon';
19
+ import { MatSelectModule } from '@angular/material/select';
20
+ import { Editor } from '@tiptap/core';
21
+ import StarterKit from '@tiptap/starter-kit';
22
+ import Placeholder from '@tiptap/extension-placeholder';
23
+ import Image from '@tiptap/extension-image';
24
+ import Link from '@tiptap/extension-link';
5
25
 
6
- declare let tinymce: any;
26
+ type BlockType = 'paragraph' | 'h1' | 'h2' | 'h3';
7
27
 
8
28
  @Component({
9
29
  selector: 'app-rich-text-editor',
10
30
  templateUrl: './rich-text-editor.component.html',
11
31
  styleUrls: ['./rich-text-editor.component.scss'],
12
- imports: [CommonModule, EditorModule, ReactiveFormsModule],
13
- providers: [{ provide: TINYMCE_SCRIPT_SRC, useValue: 'tinymce/tinymce.min.js' }]
32
+ imports: [CommonModule, ReactiveFormsModule, MatButtonModule, MatFormFieldModule, MatIconModule, MatSelectModule]
14
33
  })
15
- export class RichTextEditorComponent implements OnChanges {
34
+ export class RichTextEditorComponent implements OnChanges, AfterViewInit, OnDestroy {
16
35
  @Input() placeholder;
17
36
  @Input() disabled;
18
37
  @Input() readonly;
@@ -26,13 +45,27 @@ export class RichTextEditorComponent implements OnChanges {
26
45
  @Output() onBlur: EventEmitter<any> = new EventEmitter();
27
46
  @Output() onChange: EventEmitter<any> = new EventEmitter();
28
47
 
29
- richText = new FormControl();
30
- editorConfig: any = {};
48
+ private editorEl?: ElementRef<HTMLDivElement>;
31
49
 
32
- ngOnChanges() {
33
- if (this.required) {
34
- this.richText.addValidators(Validators.required);
50
+ richText = new FormControl('');
51
+ editor?: Editor;
52
+ currentBlock: BlockType = 'paragraph';
53
+
54
+ private viewInitialized = false;
55
+
56
+ constructor(private cdr: ChangeDetectorRef) {}
57
+
58
+ @ViewChild('editorEl')
59
+ set editorElement(element: ElementRef<HTMLDivElement> | undefined) {
60
+ this.editorEl = element;
61
+ if (element && this.viewInitialized && !this.readonly && !this.editor) {
62
+ this.initializeEditor();
35
63
  }
64
+ }
65
+
66
+ ngOnChanges(changes: SimpleChanges) {
67
+ this.richText.setValidators(this.required ? Validators.required : null);
68
+ this.richText.updateValueAndValidity({ emitEvent: false });
36
69
 
37
70
  if (this.disabled) {
38
71
  this.richText.disable();
@@ -40,79 +73,198 @@ export class RichTextEditorComponent implements OnChanges {
40
73
  this.richText.enable();
41
74
  }
42
75
 
43
- if (this.value) {
44
- this.richText.setValue(this.value);
76
+ this.richText.setValue(this.value ?? '', { emitEvent: false });
77
+
78
+ if (!this.viewInitialized) return;
79
+
80
+ if (this.readonly) {
81
+ this.destroyEditor();
82
+ return;
45
83
  }
46
84
 
47
- const themeElement = document.querySelector('.dark') || document.body;
48
- let textColor = getComputedStyle(themeElement).getPropertyValue('--mat-sys-on-surface').trim();
49
- if (!textColor) textColor = '#000000';
50
-
51
- this.editorConfig = {
52
- base_url: '/tinymce',
53
- suffix: '.min',
54
- menubar: false,
55
- placeholder: this.placeholder,
56
- statusbar: false,
57
- min_height: 130,
58
- plugins: ['lists', 'advlist', 'autolink', 'image', 'link', 'autoresize'],
59
- autoresize_bottom_margin: 0,
60
- toolbar: this.disabled ? false : 'blocks | bold italic strikethrough | bullist numlist outdent indent | link image',
61
- toolbar_location: 'bottom',
62
- content_style: `
63
- body {
64
- font-family: Helvetica, Arial, sans-serif;
65
- font-size: 14px;
66
- color: ${textColor} !important;
67
- background: transparent !important;
68
- }
69
- `,
70
- branding: false,
71
- paste_data_images: true,
72
- file_picker_types: 'image',
73
- file_picker_callback: this.filePickerCallback
74
- };
75
- }
76
-
77
- filePickerCallback = cb => {
85
+ if (!this.editor && this.editorEl) {
86
+ this.initializeEditor();
87
+ }
88
+
89
+ if (this.editor && changes['value'] && this.value !== this.editor.getHTML()) {
90
+ this.editor.commands.setContent(this.value ?? '', { emitUpdate: false });
91
+ }
92
+ if (this.editor && changes['disabled']) {
93
+ this.editor.setEditable(!this.disabled);
94
+ }
95
+ }
96
+
97
+ ngAfterViewInit(): void {
98
+ this.viewInitialized = true;
99
+ if (!this.readonly) this.initializeEditor();
100
+ }
101
+
102
+ private initializeEditor(): void {
103
+ if (!this.editorEl || this.editor) return;
104
+
105
+ this.editor = new Editor({
106
+ element: this.editorEl.nativeElement,
107
+ extensions: [
108
+ StarterKit,
109
+ Placeholder.configure({ placeholder: this.placeholder || '' }),
110
+ Image.configure({ inline: false, allowBase64: true }),
111
+ Link.configure({ openOnClick: false, autolink: true })
112
+ ],
113
+ content: this.value || '',
114
+ editable: !this.disabled,
115
+ onUpdate: ({ editor }) => {
116
+ const html = editor.getHTML();
117
+ this.richText.setValue(html, { emitEvent: false });
118
+ this.richText.markAsDirty();
119
+ this.onChange.emit(html);
120
+ this.cdr.markForCheck();
121
+ },
122
+ onBlur: ({ editor }) => {
123
+ this.richText.markAsTouched();
124
+ this.onBlur.emit(editor.getHTML());
125
+ this.cdr.markForCheck();
126
+ },
127
+ onSelectionUpdate: ({ editor }) => {
128
+ this.updateCurrentBlock(editor);
129
+ this.cdr.markForCheck();
130
+ }
131
+ });
132
+ this.updateCurrentBlock(this.editor);
133
+ }
134
+
135
+ private updateCurrentBlock(editor: Editor): void {
136
+ if (editor.isActive('heading', { level: 1 })) this.currentBlock = 'h1';
137
+ else if (editor.isActive('heading', { level: 2 })) this.currentBlock = 'h2';
138
+ else if (editor.isActive('heading', { level: 3 })) this.currentBlock = 'h3';
139
+ else this.currentBlock = 'paragraph';
140
+ }
141
+
142
+ private destroyEditor(): void {
143
+ this.editor?.destroy();
144
+ this.editor = undefined;
145
+ this.currentBlock = 'paragraph';
146
+ }
147
+
148
+ ngOnDestroy(): void {
149
+ this.destroyEditor();
150
+ }
151
+
152
+ isActive(name: string, attrs?: Record<string, any>): boolean {
153
+ return this.editor ? this.editor.isActive(name, attrs) : false;
154
+ }
155
+
156
+ setBlock(value: string) {
157
+ if (!this.editor) return;
158
+ const chain = this.editor.chain().focus();
159
+ switch (value) {
160
+ case 'h1':
161
+ chain.toggleHeading({ level: 1 }).run();
162
+ break;
163
+ case 'h2':
164
+ chain.toggleHeading({ level: 2 }).run();
165
+ break;
166
+ case 'h3':
167
+ chain.toggleHeading({ level: 3 }).run();
168
+ break;
169
+ default:
170
+ chain.setParagraph().run();
171
+ }
172
+ this.currentBlock = value as BlockType;
173
+ }
174
+
175
+ toggleBold() {
176
+ this.editor?.chain().focus().toggleBold().run();
177
+ }
178
+
179
+ toggleItalic() {
180
+ this.editor?.chain().focus().toggleItalic().run();
181
+ }
182
+
183
+ toggleStrike() {
184
+ this.editor?.chain().focus().toggleStrike().run();
185
+ }
186
+
187
+ toggleBulletList() {
188
+ this.editor?.chain().focus().toggleBulletList().run();
189
+ }
190
+
191
+ toggleOrderedList() {
192
+ this.editor?.chain().focus().toggleOrderedList().run();
193
+ }
194
+
195
+ outdent() {
196
+ if (!this.editor) return;
197
+ if (this.editor.can().liftListItem('listItem')) {
198
+ this.editor.chain().focus().liftListItem('listItem').run();
199
+ } else {
200
+ this.editor.chain().focus().liftEmptyBlock().run();
201
+ }
202
+ }
203
+
204
+ indent() {
205
+ this.editor?.chain().focus().sinkListItem('listItem').run();
206
+ }
207
+
208
+ addLink() {
209
+ if (!this.editor) return;
210
+ const previous = this.editor.getAttributes('link')?.['href'] ?? '';
211
+ const { from, to, empty } = this.editor.state.selection;
212
+ const url = window.prompt('Enter URL', previous);
213
+ if (url === null) return;
214
+ if (url === '') {
215
+ this.editor.chain().focus().setTextSelection({ from, to }).extendMarkRange('link').unsetLink().run();
216
+ return;
217
+ }
218
+ const normalizedUrl = this.normalizeUrl(url);
219
+ if (!normalizedUrl) return;
220
+ if (empty) {
221
+ this.editor
222
+ .chain()
223
+ .focus()
224
+ .insertContentAt(from, {
225
+ type: 'text',
226
+ text: url,
227
+ marks: [{ type: 'link', attrs: { href: normalizedUrl } }]
228
+ })
229
+ .run();
230
+ return;
231
+ }
232
+ this.editor.chain().focus().setTextSelection({ from, to }).extendMarkRange('link').setLink({ href: normalizedUrl }).run();
233
+ }
234
+
235
+ private normalizeUrl(url: string): string | null {
236
+ const trimmedUrl = url.trim();
237
+ if (!trimmedUrl) return null;
238
+ if (/^\/\//.test(trimmedUrl)) return `https:${trimmedUrl}`;
239
+ if (/^(?:https?|mailto|tel|ftp):/i.test(trimmedUrl)) return trimmedUrl;
240
+ if (/^[a-z][a-z\d+.-]*:/i.test(trimmedUrl)) return null;
241
+ return `https://${trimmedUrl}`;
242
+ }
243
+
244
+ addImage() {
245
+ if (!this.editor) return;
246
+ const insertPosition = this.editor.state.selection.to;
78
247
  const input = document.createElement('input');
79
248
  input.setAttribute('type', 'file');
80
249
  input.setAttribute('accept', 'image/*');
81
250
 
82
251
  input.addEventListener('change', (e: any) => {
83
- const file = e.target.files[0];
84
-
85
- const reader: any = new FileReader();
252
+ const file = e.target.files?.[0];
253
+ if (!file) return;
254
+ const reader = new FileReader();
86
255
  reader.addEventListener('load', () => {
87
- /*
88
- Note: Now we need to register the blob in TinyMCEs image blob
89
- registry. In the next release this part hopefully won't be
90
- necessary, as we are looking to handle it internally.
91
- */
92
- const blobId = `blobid${new Date().getTime()}`;
93
- console.log('editorRef', tinymce.activeEditor);
94
- const blobCache = tinymce.activeEditor.editorUpload.blobCache;
95
- const base64 = reader.result.split(',')[1];
96
- const blobInfo = blobCache.create(blobId, file, base64);
97
- blobCache.add(blobInfo);
98
-
99
- /* call the callback and populate the Title field with the file name */
100
- cb(blobInfo.blobUri(), { title: file.name });
256
+ const src = reader.result as string;
257
+ this.editor
258
+ ?.chain()
259
+ .focus()
260
+ .setTextSelection(insertPosition)
261
+ .insertContent({ type: 'image', attrs: { src, alt: file.name, title: file.name } })
262
+ .run();
101
263
  });
264
+ reader.addEventListener('error', () => this.cdr.markForCheck());
102
265
  reader.readAsDataURL(file);
103
266
  });
104
267
 
105
268
  input.click();
106
- };
107
-
108
- blur() {
109
- if (tinymce.activeEditor) {
110
- const editorValue = tinymce.activeEditor.getContent({ format: 'html' });
111
- this.onBlur.emit(editorValue);
112
- }
113
- }
114
-
115
- change(event) {
116
- this.onChange.emit(event);
117
269
  }
118
270
  }
@@ -4,7 +4,7 @@
4
4
  <ng-template #noDisplayMode>
5
5
  <div *ngIf="!bReadonly$ && bHasForm$; else noEdit">
6
6
  <div [formGroup]="formGroup$" *ngIf="bVisible$">
7
- <mat-form-field class="psdk-full-width" subscriptSizing="dynamic" [hintLabel]="helperText">
7
+ <mat-form-field class="psdk-full-width" subscriptSizing="dynamic">
8
8
  <mat-label>{{ label$ }}</mat-label>
9
9
  <input
10
10
  matInput
@@ -15,11 +15,52 @@
15
15
  [matAutocomplete]="auto"
16
16
  (input)="fieldOnChange($event)"
17
17
  />
18
- <mat-autocomplete #auto="matAutocomplete" autoActiveFirstOption (optionSelected)="optionChanged($event)">
19
- <mat-option *ngFor="let opt of filteredOptions | async" [value]="opt.value">
20
- <span>{{ opt.value }}</span>
21
- </mat-option>
18
+ <mat-autocomplete #auto="matAutocomplete" [class]="'psdk-autocomplete-panel'" autoActiveFirstOption (optionSelected)="optionChanged($event)">
19
+ <ng-container *ngIf="hasGroupBy; else flatOptionsList">
20
+ <mat-optgroup *ngFor="let group of groupedFilteredOptions$ | async" [label]="group.label">
21
+ <mat-option *ngFor="let opt of group.options" [value]="opt.value">
22
+ <span class="psdk-autocomplete-option-primary">{{ opt.value }}</span>
23
+ <div class="psdk-autocomplete-option-secondary" *ngIf="opt.secondaryComponents?.length">
24
+ <ng-container *ngFor="let secondary of opt.secondaryComponents; let last = last">
25
+ <component-mapper
26
+ [name]="secondary.getPConnect().getComponentName()"
27
+ [props]="{ pConn$: secondary.getPConnect(), formGroup$ }"
28
+ ></component-mapper>
29
+ <span *ngIf="!last" class="psdk-autocomplete-option-separator" aria-hidden="true"> · </span>
30
+ </ng-container>
31
+ </div>
32
+ </mat-option>
33
+ </mat-optgroup>
34
+ </ng-container>
35
+ <ng-template #flatOptionsList>
36
+ <mat-option *ngFor="let opt of filteredOptions | async" [value]="opt.value">
37
+ <span class="psdk-autocomplete-option-primary">{{ opt.value }}</span>
38
+ <div class="psdk-autocomplete-option-secondary" *ngIf="opt.secondaryComponents?.length">
39
+ <ng-container *ngFor="let secondary of opt.secondaryComponents; let last = last">
40
+ <component-mapper
41
+ [name]="secondary.getPConnect().getComponentName()"
42
+ [props]="{ pConn$: secondary.getPConnect(), formGroup$ }"
43
+ ></component-mapper>
44
+ <span *ngIf="!last" class="psdk-autocomplete-option-separator" aria-hidden="true"> · </span>
45
+ </ng-container>
46
+ </div>
47
+ </mat-option>
48
+ </ng-template>
49
+ <div *ngIf="showCreateButton" class="psdk-autocomplete-create-new-wrapper">
50
+ <mat-divider></mat-divider>
51
+ <button
52
+ mat-button
53
+ type="button"
54
+ class="psdk-autocomplete-create-new"
55
+ (mousedown)="$event.preventDefault()"
56
+ (click)="createNewButtonHandler()"
57
+ >
58
+ <mat-icon>add</mat-icon>
59
+ {{ createNewLabel }}
60
+ </button>
61
+ </div>
22
62
  </mat-autocomplete>
63
+ <mat-hint *ngIf="helperText" [appFieldWarning]="bFieldMessageVisible$">{{ helperText }}</mat-hint>
23
64
  <mat-error *ngIf="fieldControl.invalid">{{ getErrorMessage() }}</mat-error>
24
65
  </mat-form-field>
25
66
  </div>
@@ -18,3 +18,95 @@
18
18
  ::ng-deep .mat-mdc-form-field-infix {
19
19
  width: auto;
20
20
  }
21
+
22
+ // Scoped to this component's own panel via mat-autocomplete's [class] input — does not affect
23
+ // other mat-option/mat-autocomplete instances in the app (research.md §3).
24
+ ::ng-deep .psdk-autocomplete-panel {
25
+ overflow-x: hidden;
26
+
27
+ .mat-mdc-option {
28
+ .mdc-list-item__primary-text {
29
+ width: 100%;
30
+ white-space: normal;
31
+ overflow: visible;
32
+ text-overflow: unset;
33
+ }
34
+
35
+ // Only grow/top-align options that actually render secondary content — leaves options
36
+ // without secondary text at Material's default single-line height/vertical centering.
37
+ &:has(.psdk-autocomplete-option-secondary) {
38
+ height: auto;
39
+ min-height: 48px;
40
+ align-items: flex-start;
41
+ padding-top: 8px;
42
+ padding-bottom: 8px;
43
+ }
44
+ }
45
+ }
46
+
47
+ .psdk-autocomplete-option-secondary {
48
+ display: flex;
49
+ flex-wrap: wrap;
50
+ align-items: baseline;
51
+ column-gap: 0.25rem;
52
+ width: 100%;
53
+ overflow-wrap: break-word;
54
+ word-break: break-word;
55
+ color: var(--mat-sys-on-surface-variant);
56
+
57
+ // Each secondary field renders through FieldValueList's own DISPLAY_ONLY template, which
58
+ // defaults to a two-column grid block (.psdk-container-labels-left) or an unset-display
59
+ // block (.psdk-container-nolabels). Override both to render inline here so multiple fields
60
+ // join on one line via the `·` separators, instead of stacking as separate block rows
61
+ // (FR-002/FR-004). FieldValueListComponent itself is not modified (research.md §6).
62
+ ::ng-deep .psdk-container-labels-left {
63
+ display: inline-flex;
64
+ column-gap: 0.25rem;
65
+ row-gap: 0;
66
+ padding-block: 0;
67
+ }
68
+
69
+ ::ng-deep .psdk-container-nolabels {
70
+ display: inline;
71
+ padding-block: 0;
72
+ }
73
+
74
+ ::ng-deep .psdk-grid-label::after {
75
+ content: ':';
76
+ }
77
+
78
+ ::ng-deep .psdk-val-labels-left {
79
+ white-space: normal;
80
+ }
81
+ }
82
+
83
+ ::ng-deep .psdk-autocomplete-create-new-wrapper {
84
+ position: sticky;
85
+ bottom: 0;
86
+ z-index: 1;
87
+ background-color: var(--mat-sys-surface-container, var(--mat-sys-surface, #fff));
88
+
89
+ .mat-divider {
90
+ margin: 0;
91
+ }
92
+
93
+ .psdk-autocomplete-create-new {
94
+ display: flex;
95
+ align-items: center;
96
+ justify-content: flex-start;
97
+ width: 100%;
98
+ height: 48px;
99
+ padding: 0 16px;
100
+ text-align: left;
101
+ text-transform: none;
102
+ color: var(--mat-sys-primary);
103
+
104
+ .mat-mdc-button-touch-target {
105
+ width: 100%;
106
+ }
107
+
108
+ .mat-icon {
109
+ margin-right: 8px;
110
+ }
111
+ }
112
+ }