@siemens/element-ng 48.3.0 → 48.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/accordion/index.d.ts +0 -1
  2. package/chat-messages/index.d.ts +172 -156
  3. package/dashboard/index.d.ts +2 -3
  4. package/fesm2022/siemens-element-ng-accordion.mjs +2 -3
  5. package/fesm2022/siemens-element-ng-accordion.mjs.map +1 -1
  6. package/fesm2022/siemens-element-ng-chat-messages.mjs +259 -163
  7. package/fesm2022/siemens-element-ng-chat-messages.mjs.map +1 -1
  8. package/fesm2022/siemens-element-ng-dashboard.mjs +7 -11
  9. package/fesm2022/siemens-element-ng-dashboard.mjs.map +1 -1
  10. package/fesm2022/siemens-element-ng-datepicker.mjs +5 -3
  11. package/fesm2022/siemens-element-ng-datepicker.mjs.map +1 -1
  12. package/fesm2022/siemens-element-ng-filtered-search.mjs +4 -8
  13. package/fesm2022/siemens-element-ng-filtered-search.mjs.map +1 -1
  14. package/fesm2022/siemens-element-ng-navbar-vertical.mjs +2 -9
  15. package/fesm2022/siemens-element-ng-navbar-vertical.mjs.map +1 -1
  16. package/fesm2022/siemens-element-ng-select.mjs +4 -0
  17. package/fesm2022/siemens-element-ng-select.mjs.map +1 -1
  18. package/fesm2022/siemens-element-ng-side-panel.mjs +3 -5
  19. package/fesm2022/siemens-element-ng-side-panel.mjs.map +1 -1
  20. package/fesm2022/siemens-element-ng-status-bar.mjs +3 -4
  21. package/fesm2022/siemens-element-ng-status-bar.mjs.map +1 -1
  22. package/fesm2022/siemens-element-ng-tour.mjs +58 -21
  23. package/fesm2022/siemens-element-ng-tour.mjs.map +1 -1
  24. package/fesm2022/siemens-element-ng-translate.mjs.map +1 -1
  25. package/fesm2022/siemens-element-ng-tree-view.mjs +4 -4
  26. package/fesm2022/siemens-element-ng-tree-view.mjs.map +1 -1
  27. package/filtered-search/index.d.ts +6 -7
  28. package/navbar-vertical/index.d.ts +2 -4
  29. package/package.json +7 -7
  30. package/schematics/migrations/action-modal-migration/action-modal-migration.js +45 -4
  31. package/schematics/migrations/data/output-names.js +0 -1
  32. package/schematics/migrations/data/symbol-removals.js +0 -9
  33. package/schematics/migrations/element-migration/element-migration.js +1 -9
  34. package/schematics/migrations/wizard-migration/index.js +1 -9
  35. package/select/index.d.ts +5 -0
  36. package/status-bar/index.d.ts +0 -1
  37. package/template-i18n.json +1 -0
  38. package/tour/index.d.ts +4 -2
  39. package/translate/index.d.ts +1 -0
@@ -1,12 +1,13 @@
1
1
  import { CdkMenuTrigger } from '@angular/cdk/menu';
2
2
  import * as i0 from '@angular/core';
3
- import { Directive, input, Component, viewChild, signal, effect, booleanAttribute, inject, output, model, computed } from '@angular/core';
3
+ import { Directive, input, Component, viewChild, signal, effect, booleanAttribute, inject, output, PLATFORM_ID, model, computed } from '@angular/core';
4
4
  import { SiIconComponent } from '@siemens/element-ng/icon';
5
5
  import { SiMenuFactoryComponent } from '@siemens/element-ng/menu';
6
6
  import { t, SiTranslatePipe } from '@siemens/element-translate-ng/translate';
7
7
  import * as i1 from '@siemens/element-ng/resize-observer';
8
8
  import { SiResponsiveContainerDirective } from '@siemens/element-ng/resize-observer';
9
9
  import { SiModalService } from '@siemens/element-ng/modal';
10
+ import { isPlatformBrowser } from '@angular/common';
10
11
  import * as i1$1 from '@angular/forms';
11
12
  import { FormsModule } from '@angular/forms';
12
13
  import { SiFileUploadDirective } from '@siemens/element-ng/file-uploader';
@@ -16,10 +17,9 @@ import { SiFileUploadDirective } from '@siemens/element-ng/file-uploader';
16
17
  * SPDX-License-Identifier: MIT
17
18
  */
18
19
  /**
19
- * Directive to mark content as chat message actions.
20
+ * Directive to mark content as chat message actions into {@link SiChatMessageComponent}.
20
21
  * Apply this directive to e.g. buttons that should be slotted into the message actions area.
21
22
  *
22
- * @experimental
23
23
  * @example
24
24
  * ```html
25
25
  * <si-chat-message>
@@ -28,6 +28,10 @@ import { SiFileUploadDirective } from '@siemens/element-ng/file-uploader';
28
28
  * <button siChatMessageAction>Share</button>
29
29
  * </si-chat-message>
30
30
  * ```
31
+ *
32
+ * @see {@link SiChatMessageComponent} for the chat message wrapper component
33
+ *
34
+ * @experimental
31
35
  */
32
36
  class SiChatMessageActionDirective {
33
37
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: SiChatMessageActionDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
@@ -45,23 +49,33 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.6", ngImpor
45
49
  * SPDX-License-Identifier: MIT
46
50
  */
47
51
  /**
48
- * Base chat message component that provides the layout structure for conversational interfaces.
52
+ * Base declarative chat message component that provides the layout structure for chat messages.
49
53
  *
50
54
  * This component handles the core message layout including avatar positioning, loading states,
51
- * and action button placement. It serves as the foundation for more specialized message components
55
+ * and action button as well as attachment list placement. It serves as the foundation for more specialized message components
52
56
  * like {@link SiUserMessageComponent} and {@link SiAiMessageComponent}.
57
+ * Can be used within {@link SiChatContainerComponent}.
53
58
  *
54
- * @remarks
55
59
  * The component provides:
56
60
  * - Flexible alignment (start/end) for different message types
57
61
  * - Avatar/icon slot for message attribution
58
62
  * - Loading state with skeleton UI
59
63
  * - Action buttons positioned on the side or bottom
64
+ * - Attachment list display slot
60
65
  * - Responsive behavior that adapts to container size
61
- * - Attachment display slot
62
66
  *
63
- * This is a low-level component typically not used directly. Instead, use the higher-level
64
- * message components that wrap this component with specific styling and behavior.
67
+ * This is a low-level component designed for slotting in custom content, it provides slots via content projection:
68
+ * - Default content: Main message content area (consider using {@link SiMarkdownRendererComponent} for markdown support)
69
+ * - `si-avatar/si-icon/img` selector: Avatar or icon representing the message sender
70
+ * - `si-chat-message-action` selector: Action buttons related to the message
71
+ * - `si-attachment-list` selector: Attachment list component for displaying file attachments
72
+ *
73
+ * @see {@link SiUserMessageComponent} for user message display
74
+ * @see {@link SiAiMessageComponent} for AI message display
75
+ * @see {@link SiAttachmentListComponent} for attachment list to slot in
76
+ * @see {@link SiChatMessageActionDirective} for action buttons to slot in
77
+ * @see {@link SiMarkdownRendererComponent} for markdown content rendering
78
+ * @see {@link SiChatContainerComponent} for the chat container to use this within
65
79
  *
66
80
  * @experimental
67
81
  */
@@ -82,13 +96,13 @@ class SiChatMessageComponent {
82
96
  */
83
97
  actionsPosition = input('side');
84
98
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: SiChatMessageComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
85
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.6", type: SiChatMessageComponent, isStandalone: true, selector: "si-chat-message", inputs: { loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, alignment: { classPropertyName: "alignment", publicName: "alignment", isSignal: true, isRequired: false, transformFunction: null }, actionsPosition: { classPropertyName: "actionsPosition", publicName: "actionsPosition", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "d-block" }, hostDirectives: [{ directive: i1.SiResponsiveContainerDirective }], ngImport: i0, template: "<!--- Flex-row if alignment start, flex-row-reverse if alignment end, flex-column if mobile -->\n<div class=\"d-flex si-body-2 chat-message-container\" [class.start]=\"alignment() === 'start'\">\n <div class=\"avatar-wrapper flex-shrink-0\" [class.end]=\"alignment() === 'end'\">\n <ng-content select=\"si-icon,si-avatar,img\" />\n </div>\n\n <div class=\"d-flex flex-column flex-grow-1 w-100\">\n <div class=\"attachment-slot\" [class.align-self-end]=\"alignment() === 'end'\">\n <ng-content select=\"si-attachment-list,si-badge\" />\n </div>\n\n @if (loading()) {\n <div\n class=\"message-wrapper rounded-3 w-75 text-break loading-message-bubble mb-2\"\n [class.align-self-end]=\"alignment() === 'end'\"\n >\n <div class=\"d-flex flex-column w-100 gap-2\">\n <div class=\"si-skeleton skeleton-line skeleton-line-full\"></div>\n <div class=\"si-skeleton skeleton-line skeleton-line-half\"></div>\n </div>\n </div>\n } @else {\n <!-- Flex-column if actions bottom, flex-row/flex-row-reverse if actions start -->\n <div\n class=\"message-wrapper mw-0 d-flex mb-2\"\n [class.end]=\"alignment() === 'end'\"\n [class.flex-column]=\"actionsPosition() === 'bottom'\"\n [class.flex-row]=\"actionsPosition() === 'side' && alignment() === 'start'\"\n [class.flex-row-reverse]=\"actionsPosition() === 'side' && alignment() === 'end'\"\n [class.align-items-start]=\"actionsPosition() === 'side'\"\n [class.align-items-end]=\"actionsPosition() === 'bottom' && alignment() === 'end'\"\n [class.justify-content-end]=\"alignment() === 'end' && actionsPosition() === 'bottom'\"\n [class.justify-content-start]=\"alignment() === 'start' && actionsPosition() === 'bottom'\"\n >\n <div\n class=\"rounded-3 text-break message-bubble\"\n [class.end]=\"alignment() === 'end' && actionsPosition() === 'bottom'\"\n >\n <ng-content />\n </div>\n\n <div\n class=\"actions-wrapper d-flex gap-4\"\n [class.ms-3]=\"actionsPosition() === 'side' && alignment() === 'start'\"\n [class.me-3]=\"actionsPosition() === 'side' && alignment() === 'end'\"\n [class.mt-2]=\"actionsPosition() === 'bottom'\"\n [class.actions-horizontal]=\"actionsPosition() !== 'bottom'\"\n [class.align-self-start]=\"actionsPosition() === 'side'\"\n >\n <ng-content select=\"[siChatMessageAction]\" />\n </div>\n </div>\n }\n </div>\n</div>\n", styles: [":host{display:block;--chat-message-bubble-bg: var(--element-base-1);--chat-message-bubble-padding: 12px}:host ::ng-deep si-loading-spinner{--loading-spinner-size: 1.5em}.skeleton-line-full{inline-size:100%}.skeleton-line-half{inline-size:50%}.loading-message-bubble,.message-bubble{padding:var(--chat-message-bubble-padding)}.loading-message-bubble{inline-size:max-content;min-inline-size:75%;margin-block-end:auto;background-color:var(--chat-message-bubble-bg)}.message-bubble{margin-block-end:auto;background-color:var(--chat-message-bubble-bg);min-inline-size:0;overflow-wrap:break-word;word-break:break-word}.message-bubble:empty{display:none}.message-bubble:empty~.actions-wrapper{margin-inline:0!important}.message-wrapper{min-inline-size:0}.attachment-slot:empty{display:none}.attachment-slot:not(:empty)~.message-wrapper:not(:has(.message-bubble:empty)){margin-block-start:4px}.attachment-slot:not(:empty)~.message-wrapper:has(.message-bubble:empty) .actions-wrapper.actions-horizontal{margin-block-start:4px}.actions-wrapper:empty{display:none!important}.avatar-wrapper{align-self:flex-start}.avatar-wrapper:not(.end){margin-inline-end:6px}.avatar-wrapper.end{margin-inline-start:6px}.avatar-wrapper:empty{display:none}:host-context(.si-container-md,.si-container-lg,.si-container-xl,.si-container-xxl) .chat-message-container{flex-direction:row}:host-context(.si-container-md,.si-container-lg,.si-container-xl,.si-container-xxl) .chat-message-container.start{align-items:flex-start}:host-context(.si-container-md,.si-container-lg,.si-container-xl,.si-container-xxl) .chat-message-container:not(.start){flex-direction:row-reverse}:host-context(.si-container-md,.si-container-lg,.si-container-xl,.si-container-xxl) .message-wrapper{min-inline-size:0;inline-size:auto}:host-context(.si-container-md,.si-container-lg,.si-container-xl,.si-container-xxl) .message-wrapper.end{margin-inline-start:auto;display:flex;flex-direction:column;align-items:flex-end}:host-context(.si-container-md,.si-container-lg,.si-container-xl,.si-container-xxl) .message-bubble.end{margin-inline-start:auto}.attachment-slot ::ng-deep si-attachment-list{--attachment-list-bg: transparent;--attachment-name-color: var(--element-text-secondary)}:host-context(.si-container-xs,.si-container-sm) .message-wrapper{min-inline-size:0}:host-context(.si-container-xs,.si-container-sm) .message-wrapper.end{margin-inline-start:auto}:host-context(.si-container-xs,.si-container-sm) .chat-message-container{flex-direction:column}:host-context(.si-container-xs,.si-container-sm) .avatar-wrapper{margin-block-end:6px}:host-context(.si-container-xs,.si-container-sm) .avatar-wrapper.end{align-self:flex-end}:host-context(.si-container-xs,.si-container-sm) .avatar-wrapper::ng-deep:has(si-icon) .end{margin-inline-end:4px!important}:host-context(.si-container-xs,.si-container-sm) .avatar-wrapper::ng-deep:has(si-icon):not(.end){margin-inline-start:4px!important}\n"] });
99
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.6", type: SiChatMessageComponent, isStandalone: true, selector: "si-chat-message", inputs: { loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, alignment: { classPropertyName: "alignment", publicName: "alignment", isSignal: true, isRequired: false, transformFunction: null }, actionsPosition: { classPropertyName: "actionsPosition", publicName: "actionsPosition", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "d-block" }, hostDirectives: [{ directive: i1.SiResponsiveContainerDirective }], ngImport: i0, template: "<!--- Flex-row if alignment start, flex-row-reverse if alignment end, flex-column if mobile -->\n<div class=\"d-flex si-body-2 chat-message-container\" [class.start]=\"alignment() === 'start'\">\n <div class=\"avatar-wrapper flex-shrink-0\" [class.end]=\"alignment() === 'end'\">\n <ng-content select=\"si-icon,si-avatar,img\" />\n </div>\n\n <div class=\"d-flex flex-column flex-grow-1 w-100\">\n <div class=\"attachment-slot\" [class.align-self-end]=\"alignment() === 'end'\">\n <ng-content select=\"si-attachment-list,si-badge\" />\n </div>\n\n @if (loading()) {\n <div\n class=\"message-wrapper rounded-3 w-75 text-break loading-message-bubble mb-2\"\n [class.align-self-end]=\"alignment() === 'end'\"\n >\n <div class=\"d-flex flex-column w-100 gap-2\">\n <div class=\"si-skeleton skeleton-line skeleton-line-full\"></div>\n <div class=\"si-skeleton skeleton-line skeleton-line-half\"></div>\n </div>\n </div>\n } @else {\n <!-- Flex-column if actions bottom, flex-row/flex-row-reverse if actions start -->\n <div\n class=\"message-wrapper mw-0 d-flex mb-2\"\n [class.start]=\"alignment() === 'start'\"\n [class.end]=\"alignment() === 'end'\"\n [class.flex-column]=\"actionsPosition() === 'bottom'\"\n [class.flex-row]=\"actionsPosition() === 'side' && alignment() === 'start'\"\n [class.flex-row-reverse]=\"actionsPosition() === 'side' && alignment() === 'end'\"\n [class.align-items-start]=\"actionsPosition() === 'side'\"\n [class.align-items-end]=\"actionsPosition() === 'bottom' && alignment() === 'end'\"\n [class.justify-content-end]=\"alignment() === 'end' && actionsPosition() === 'bottom'\"\n [class.justify-content-start]=\"alignment() === 'start' && actionsPosition() === 'bottom'\"\n >\n <div\n class=\"rounded-3 text-break message-bubble\"\n [class.end]=\"alignment() === 'end' && actionsPosition() === 'bottom'\"\n >\n <ng-content />\n </div>\n\n <div\n class=\"actions-wrapper d-flex gap-4\"\n [class.ms-3]=\"actionsPosition() === 'side' && alignment() === 'start'\"\n [class.me-3]=\"actionsPosition() === 'side' && alignment() === 'end'\"\n [class.mt-2]=\"actionsPosition() === 'bottom'\"\n [class.actions-horizontal]=\"actionsPosition() !== 'bottom'\"\n [class.align-self-start]=\"actionsPosition() === 'side'\"\n >\n <ng-content select=\"[siChatMessageAction]\" />\n </div>\n </div>\n }\n </div>\n</div>\n", styles: [":host{display:block;--chat-message-bubble-bg: var(--element-base-1);--chat-message-bubble-padding: 12px}:host ::ng-deep si-loading-spinner{--loading-spinner-size: 1.5em}.skeleton-line-full{inline-size:100%}.skeleton-line-half{inline-size:50%}.loading-message-bubble,.message-bubble{padding:var(--chat-message-bubble-padding)}.loading-message-bubble{inline-size:max-content;min-inline-size:75%;margin-block-end:auto;background-color:var(--chat-message-bubble-bg)}.message-bubble{margin-block-end:auto;background-color:var(--chat-message-bubble-bg);min-inline-size:0;overflow-wrap:break-word;word-break:break-word}.message-bubble:empty{display:none}.message-bubble:empty~.actions-wrapper{margin-inline:0!important}.message-wrapper{min-inline-size:0}.attachment-slot:empty{display:none}.attachment-slot:not(:empty)~.message-wrapper:not(:has(.message-bubble:empty)){margin-block-start:4px}.attachment-slot:not(:empty)~.message-wrapper:has(.message-bubble:empty) .actions-wrapper.actions-horizontal{margin-block-start:4px}.actions-wrapper:empty{display:none!important}.avatar-wrapper{align-self:flex-start}.avatar-wrapper:not(.end){margin-inline-end:6px}.avatar-wrapper.end{margin-inline-start:6px}.avatar-wrapper:empty{display:none}.message-wrapper{min-inline-size:0;inline-size:auto}.message-wrapper.start{margin-inline-end:auto}.message-wrapper.end{margin-inline-start:auto}:host-context(.si-container-md,.si-container-lg,.si-container-xl,.si-container-xxl) .chat-message-container{flex-direction:row}:host-context(.si-container-md,.si-container-lg,.si-container-xl,.si-container-xxl) .chat-message-container.start{align-items:flex-start}:host-context(.si-container-md,.si-container-lg,.si-container-xl,.si-container-xxl) .chat-message-container:not(.start){flex-direction:row-reverse}:host-context(.si-container-md,.si-container-lg,.si-container-xl,.si-container-xxl) .message-bubble.end{margin-inline-start:auto}.attachment-slot ::ng-deep si-attachment-list{--attachment-list-bg: transparent;--attachment-name-color: var(--element-text-secondary)}:host-context(.si-container-xs,.si-container-sm) .chat-message-container{flex-direction:column}:host-context(.si-container-xs,.si-container-sm) .avatar-wrapper{margin-block-end:6px}:host-context(.si-container-xs,.si-container-sm) .avatar-wrapper.end{align-self:flex-end}:host-context(.si-container-xs,.si-container-sm) .avatar-wrapper::ng-deep:has(si-icon) .end{margin-inline-end:4px!important}:host-context(.si-container-xs,.si-container-sm) .avatar-wrapper::ng-deep:has(si-icon):not(.end){margin-inline-start:4px!important}\n"] });
86
100
  }
87
101
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: SiChatMessageComponent, decorators: [{
88
102
  type: Component,
89
103
  args: [{ selector: 'si-chat-message', host: {
90
104
  class: 'd-block'
91
- }, hostDirectives: [SiResponsiveContainerDirective], template: "<!--- Flex-row if alignment start, flex-row-reverse if alignment end, flex-column if mobile -->\n<div class=\"d-flex si-body-2 chat-message-container\" [class.start]=\"alignment() === 'start'\">\n <div class=\"avatar-wrapper flex-shrink-0\" [class.end]=\"alignment() === 'end'\">\n <ng-content select=\"si-icon,si-avatar,img\" />\n </div>\n\n <div class=\"d-flex flex-column flex-grow-1 w-100\">\n <div class=\"attachment-slot\" [class.align-self-end]=\"alignment() === 'end'\">\n <ng-content select=\"si-attachment-list,si-badge\" />\n </div>\n\n @if (loading()) {\n <div\n class=\"message-wrapper rounded-3 w-75 text-break loading-message-bubble mb-2\"\n [class.align-self-end]=\"alignment() === 'end'\"\n >\n <div class=\"d-flex flex-column w-100 gap-2\">\n <div class=\"si-skeleton skeleton-line skeleton-line-full\"></div>\n <div class=\"si-skeleton skeleton-line skeleton-line-half\"></div>\n </div>\n </div>\n } @else {\n <!-- Flex-column if actions bottom, flex-row/flex-row-reverse if actions start -->\n <div\n class=\"message-wrapper mw-0 d-flex mb-2\"\n [class.end]=\"alignment() === 'end'\"\n [class.flex-column]=\"actionsPosition() === 'bottom'\"\n [class.flex-row]=\"actionsPosition() === 'side' && alignment() === 'start'\"\n [class.flex-row-reverse]=\"actionsPosition() === 'side' && alignment() === 'end'\"\n [class.align-items-start]=\"actionsPosition() === 'side'\"\n [class.align-items-end]=\"actionsPosition() === 'bottom' && alignment() === 'end'\"\n [class.justify-content-end]=\"alignment() === 'end' && actionsPosition() === 'bottom'\"\n [class.justify-content-start]=\"alignment() === 'start' && actionsPosition() === 'bottom'\"\n >\n <div\n class=\"rounded-3 text-break message-bubble\"\n [class.end]=\"alignment() === 'end' && actionsPosition() === 'bottom'\"\n >\n <ng-content />\n </div>\n\n <div\n class=\"actions-wrapper d-flex gap-4\"\n [class.ms-3]=\"actionsPosition() === 'side' && alignment() === 'start'\"\n [class.me-3]=\"actionsPosition() === 'side' && alignment() === 'end'\"\n [class.mt-2]=\"actionsPosition() === 'bottom'\"\n [class.actions-horizontal]=\"actionsPosition() !== 'bottom'\"\n [class.align-self-start]=\"actionsPosition() === 'side'\"\n >\n <ng-content select=\"[siChatMessageAction]\" />\n </div>\n </div>\n }\n </div>\n</div>\n", styles: [":host{display:block;--chat-message-bubble-bg: var(--element-base-1);--chat-message-bubble-padding: 12px}:host ::ng-deep si-loading-spinner{--loading-spinner-size: 1.5em}.skeleton-line-full{inline-size:100%}.skeleton-line-half{inline-size:50%}.loading-message-bubble,.message-bubble{padding:var(--chat-message-bubble-padding)}.loading-message-bubble{inline-size:max-content;min-inline-size:75%;margin-block-end:auto;background-color:var(--chat-message-bubble-bg)}.message-bubble{margin-block-end:auto;background-color:var(--chat-message-bubble-bg);min-inline-size:0;overflow-wrap:break-word;word-break:break-word}.message-bubble:empty{display:none}.message-bubble:empty~.actions-wrapper{margin-inline:0!important}.message-wrapper{min-inline-size:0}.attachment-slot:empty{display:none}.attachment-slot:not(:empty)~.message-wrapper:not(:has(.message-bubble:empty)){margin-block-start:4px}.attachment-slot:not(:empty)~.message-wrapper:has(.message-bubble:empty) .actions-wrapper.actions-horizontal{margin-block-start:4px}.actions-wrapper:empty{display:none!important}.avatar-wrapper{align-self:flex-start}.avatar-wrapper:not(.end){margin-inline-end:6px}.avatar-wrapper.end{margin-inline-start:6px}.avatar-wrapper:empty{display:none}:host-context(.si-container-md,.si-container-lg,.si-container-xl,.si-container-xxl) .chat-message-container{flex-direction:row}:host-context(.si-container-md,.si-container-lg,.si-container-xl,.si-container-xxl) .chat-message-container.start{align-items:flex-start}:host-context(.si-container-md,.si-container-lg,.si-container-xl,.si-container-xxl) .chat-message-container:not(.start){flex-direction:row-reverse}:host-context(.si-container-md,.si-container-lg,.si-container-xl,.si-container-xxl) .message-wrapper{min-inline-size:0;inline-size:auto}:host-context(.si-container-md,.si-container-lg,.si-container-xl,.si-container-xxl) .message-wrapper.end{margin-inline-start:auto;display:flex;flex-direction:column;align-items:flex-end}:host-context(.si-container-md,.si-container-lg,.si-container-xl,.si-container-xxl) .message-bubble.end{margin-inline-start:auto}.attachment-slot ::ng-deep si-attachment-list{--attachment-list-bg: transparent;--attachment-name-color: var(--element-text-secondary)}:host-context(.si-container-xs,.si-container-sm) .message-wrapper{min-inline-size:0}:host-context(.si-container-xs,.si-container-sm) .message-wrapper.end{margin-inline-start:auto}:host-context(.si-container-xs,.si-container-sm) .chat-message-container{flex-direction:column}:host-context(.si-container-xs,.si-container-sm) .avatar-wrapper{margin-block-end:6px}:host-context(.si-container-xs,.si-container-sm) .avatar-wrapper.end{align-self:flex-end}:host-context(.si-container-xs,.si-container-sm) .avatar-wrapper::ng-deep:has(si-icon) .end{margin-inline-end:4px!important}:host-context(.si-container-xs,.si-container-sm) .avatar-wrapper::ng-deep:has(si-icon):not(.end){margin-inline-start:4px!important}\n"] }]
105
+ }, hostDirectives: [SiResponsiveContainerDirective], template: "<!--- Flex-row if alignment start, flex-row-reverse if alignment end, flex-column if mobile -->\n<div class=\"d-flex si-body-2 chat-message-container\" [class.start]=\"alignment() === 'start'\">\n <div class=\"avatar-wrapper flex-shrink-0\" [class.end]=\"alignment() === 'end'\">\n <ng-content select=\"si-icon,si-avatar,img\" />\n </div>\n\n <div class=\"d-flex flex-column flex-grow-1 w-100\">\n <div class=\"attachment-slot\" [class.align-self-end]=\"alignment() === 'end'\">\n <ng-content select=\"si-attachment-list,si-badge\" />\n </div>\n\n @if (loading()) {\n <div\n class=\"message-wrapper rounded-3 w-75 text-break loading-message-bubble mb-2\"\n [class.align-self-end]=\"alignment() === 'end'\"\n >\n <div class=\"d-flex flex-column w-100 gap-2\">\n <div class=\"si-skeleton skeleton-line skeleton-line-full\"></div>\n <div class=\"si-skeleton skeleton-line skeleton-line-half\"></div>\n </div>\n </div>\n } @else {\n <!-- Flex-column if actions bottom, flex-row/flex-row-reverse if actions start -->\n <div\n class=\"message-wrapper mw-0 d-flex mb-2\"\n [class.start]=\"alignment() === 'start'\"\n [class.end]=\"alignment() === 'end'\"\n [class.flex-column]=\"actionsPosition() === 'bottom'\"\n [class.flex-row]=\"actionsPosition() === 'side' && alignment() === 'start'\"\n [class.flex-row-reverse]=\"actionsPosition() === 'side' && alignment() === 'end'\"\n [class.align-items-start]=\"actionsPosition() === 'side'\"\n [class.align-items-end]=\"actionsPosition() === 'bottom' && alignment() === 'end'\"\n [class.justify-content-end]=\"alignment() === 'end' && actionsPosition() === 'bottom'\"\n [class.justify-content-start]=\"alignment() === 'start' && actionsPosition() === 'bottom'\"\n >\n <div\n class=\"rounded-3 text-break message-bubble\"\n [class.end]=\"alignment() === 'end' && actionsPosition() === 'bottom'\"\n >\n <ng-content />\n </div>\n\n <div\n class=\"actions-wrapper d-flex gap-4\"\n [class.ms-3]=\"actionsPosition() === 'side' && alignment() === 'start'\"\n [class.me-3]=\"actionsPosition() === 'side' && alignment() === 'end'\"\n [class.mt-2]=\"actionsPosition() === 'bottom'\"\n [class.actions-horizontal]=\"actionsPosition() !== 'bottom'\"\n [class.align-self-start]=\"actionsPosition() === 'side'\"\n >\n <ng-content select=\"[siChatMessageAction]\" />\n </div>\n </div>\n }\n </div>\n</div>\n", styles: [":host{display:block;--chat-message-bubble-bg: var(--element-base-1);--chat-message-bubble-padding: 12px}:host ::ng-deep si-loading-spinner{--loading-spinner-size: 1.5em}.skeleton-line-full{inline-size:100%}.skeleton-line-half{inline-size:50%}.loading-message-bubble,.message-bubble{padding:var(--chat-message-bubble-padding)}.loading-message-bubble{inline-size:max-content;min-inline-size:75%;margin-block-end:auto;background-color:var(--chat-message-bubble-bg)}.message-bubble{margin-block-end:auto;background-color:var(--chat-message-bubble-bg);min-inline-size:0;overflow-wrap:break-word;word-break:break-word}.message-bubble:empty{display:none}.message-bubble:empty~.actions-wrapper{margin-inline:0!important}.message-wrapper{min-inline-size:0}.attachment-slot:empty{display:none}.attachment-slot:not(:empty)~.message-wrapper:not(:has(.message-bubble:empty)){margin-block-start:4px}.attachment-slot:not(:empty)~.message-wrapper:has(.message-bubble:empty) .actions-wrapper.actions-horizontal{margin-block-start:4px}.actions-wrapper:empty{display:none!important}.avatar-wrapper{align-self:flex-start}.avatar-wrapper:not(.end){margin-inline-end:6px}.avatar-wrapper.end{margin-inline-start:6px}.avatar-wrapper:empty{display:none}.message-wrapper{min-inline-size:0;inline-size:auto}.message-wrapper.start{margin-inline-end:auto}.message-wrapper.end{margin-inline-start:auto}:host-context(.si-container-md,.si-container-lg,.si-container-xl,.si-container-xxl) .chat-message-container{flex-direction:row}:host-context(.si-container-md,.si-container-lg,.si-container-xl,.si-container-xxl) .chat-message-container.start{align-items:flex-start}:host-context(.si-container-md,.si-container-lg,.si-container-xl,.si-container-xxl) .chat-message-container:not(.start){flex-direction:row-reverse}:host-context(.si-container-md,.si-container-lg,.si-container-xl,.si-container-xxl) .message-bubble.end{margin-inline-start:auto}.attachment-slot ::ng-deep si-attachment-list{--attachment-list-bg: transparent;--attachment-name-color: var(--element-text-secondary)}:host-context(.si-container-xs,.si-container-sm) .chat-message-container{flex-direction:column}:host-context(.si-container-xs,.si-container-sm) .avatar-wrapper{margin-block-end:6px}:host-context(.si-container-xs,.si-container-sm) .avatar-wrapper.end{align-self:flex-end}:host-context(.si-container-xs,.si-container-sm) .avatar-wrapper::ng-deep:has(si-icon) .end{margin-inline-end:4px!important}:host-context(.si-container-xs,.si-container-sm) .avatar-wrapper::ng-deep:has(si-icon):not(.end){margin-inline-start:4px!important}\n"] }]
92
106
  }] });
93
107
 
94
108
  /**
@@ -101,63 +115,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.6", ngImpor
101
115
  * The AI message component renders AI-generated content in chat interfaces,
102
116
  * supporting text formatting, markdown, loading states, and contextual actions.
103
117
  * It appears as text (no bubble) aligned to the left side without any avatar/icon slot.
104
- *
105
- * @remarks
106
- * This component is designed for use in:
107
- * - AI chat interfaces where model responses need to be displayed
108
- * - Chatbot implementations
109
- * - Conversational AI applications
110
- * - AI assistant interfaces
118
+ * Can be used within {@link SiChatContainerComponent}.
111
119
  *
112
120
  * The component automatically handles:
113
- * - Rendering markdown content with syntax highlighting
121
+ * - Styling for AI messages distinct from user or generic chat messages
122
+ * - Option to render markdown content, provide via `contentFormatter` input with a markdown renderer function (e.g., from {@link getMarkdownRenderer})
114
123
  * - Showing loading states with skeleton UI during generation
115
- * - Displaying primary and secondary actions on hover (desktop) or tap (mobile)
116
- * - Proper alignment and styling for AI messages
117
- * - Content sanitization for security
118
- *
119
- * @example
120
- * Basic usage with content only:
121
- * ```html
122
- * <si-ai-message [content]="'I can help you with that.'" />
123
- * ```
124
- *
125
- * @example
126
- * With loading state and actions:
127
- * ```typescript
128
- * import { Component } from '@angular/core';
129
- * import { SiAiMessageComponent } from '@siemens/element-ng/chat-messages';
130
- *
131
- * @Component({
132
- * selector: 'app-chat',
133
- * imports: [SiAiMessageComponent],
134
- * template: `
135
- * <si-ai-message
136
- * [content]="message.text"
137
- * [loading]="message.isGenerating"
138
- * [actions]="messageActions"
139
- * [secondaryActions]="menuActions"
140
- * [actionParam]="message"
141
- * />
142
- * `
143
- * })
144
- * export class ChatComponent {
145
- * messageActions = [
146
- * { icon: 'thumbs-up', label: 'Good response', action: (id) => this.ratePositive(id) },
147
- * { icon: 'thumbs-down', label: 'Bad response', action: (id) => this.rateNegative(id) },
148
- * { icon: 'copy', label: 'Copy', action: (id) => this.copyMessage(id) }
149
- * ];
150
- *
151
- * menuActions = [
152
- * { label: 'Regenerate', action: (id) => this.regenerate(id) },
153
- * { label: 'Report', action: (id) => this.reportMessage(id) }
154
- * ];
155
- * }
156
- * ```
124
+ * - Displaying primary and secondary actions
157
125
  *
158
126
  * @see {@link SiChatMessageComponent} for the base message wrapper component
159
- * @see {@link SiUserMessageComponent} for the companion user message component
127
+ * @see {@link SiUserMessageComponent} for the user message component
160
128
  * @see {@link getMarkdownRenderer} for markdown formatting support
129
+ * @see {@link SiChatContainerComponent} for the chat container to use this within
161
130
  *
162
131
  * @experimental
163
132
  */
@@ -233,7 +202,7 @@ class SiAiMessageComponent {
233
202
  */
234
203
  secondaryActionsLabel = input(t(() => $localize `:@@SI_AI_MESSAGE.SECONDARY_ACTIONS:More actions`));
235
204
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: SiAiMessageComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
236
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.6", type: SiAiMessageComponent, isStandalone: true, selector: "si-ai-message", inputs: { content: { classPropertyName: "content", publicName: "content", isSignal: true, isRequired: false, transformFunction: null }, contentFormatter: { classPropertyName: "contentFormatter", publicName: "contentFormatter", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, actions: { classPropertyName: "actions", publicName: "actions", isSignal: true, isRequired: false, transformFunction: null }, secondaryActions: { classPropertyName: "secondaryActions", publicName: "secondaryActions", isSignal: true, isRequired: false, transformFunction: null }, actionParam: { classPropertyName: "actionParam", publicName: "actionParam", isSignal: true, isRequired: false, transformFunction: null }, secondaryActionsLabel: { classPropertyName: "secondaryActionsLabel", publicName: "secondaryActionsLabel", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "si-ai-message" }, viewQueries: [{ propertyName: "formattedContent", first: true, predicate: ["formattedContent"], descendants: true, isSignal: true }], ngImport: i0, template: "<si-chat-message alignment=\"start\" actionsPosition=\"bottom\" [loading]=\"loading()\">\n @if (content()) {\n @let content = textContent();\n @if (content) {\n <span class=\"text-pre-wrap\">{{ content }}</span>\n } @else {\n <div #formattedContent> </div>\n }\n }\n\n @if (actions().length > 0 || secondaryActions().length > 0) {\n <div class=\"d-flex gap-4 ai-message-actions\" siChatMessageAction>\n @for (action of actions(); track $index) {\n <button\n type=\"button\"\n class=\"btn btn-ghost btn-circle btn-sm\"\n [disabled]=\"action.disabled\"\n [attr.aria-label]=\"action.label | translate\"\n (click)=\"action.action(actionParam(), action)\"\n >\n <si-icon [icon]=\"action.icon\" />\n </button>\n }\n\n @if (secondaryActions().length > 0) {\n <button\n type=\"button\"\n class=\"btn btn-ghost btn-circle btn-sm\"\n [cdkMenuTriggerFor]=\"secondaryActionsMenu\"\n [attr.aria-label]=\"secondaryActionsLabel() | translate\"\n [attr.title]=\"secondaryActionsLabel() | translate\"\n >\n <si-icon icon=\"element-optionsVertical\" />\n </button>\n\n <ng-template #secondaryActionsMenu>\n <si-menu-factory [items]=\"secondaryActions()\" [actionParam]=\"actionParam()\" />\n </ng-template>\n }\n </div>\n }\n</si-chat-message>\n", styles: [":host{display:block}si-chat-message{--chat-message-bubble-bg: transparent;--chat-message-bubble-padding: 0;margin-block-end:-4px}.ai-message-actions{margin-block-start:8px}:host ::ng-deep si-loading-spinner{--loading-spinner-size: 1.5em}\n"], dependencies: [{ kind: "directive", type: CdkMenuTrigger, selector: "[cdkMenuTriggerFor]", inputs: ["cdkMenuTriggerFor", "cdkMenuPosition", "cdkMenuTriggerData"], outputs: ["cdkMenuOpened", "cdkMenuClosed"], exportAs: ["cdkMenuTriggerFor"] }, { kind: "component", type: SiChatMessageComponent, selector: "si-chat-message", inputs: ["loading", "alignment", "actionsPosition"] }, { kind: "component", type: SiIconComponent, selector: "si-icon", inputs: ["icon"] }, { kind: "component", type: SiMenuFactoryComponent, selector: "si-menu-factory", inputs: ["items", "actionParam"] }, { kind: "directive", type: SiChatMessageActionDirective, selector: "[siChatMessageAction]" }, { kind: "pipe", type: SiTranslatePipe, name: "translate" }] });
205
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.6", type: SiAiMessageComponent, isStandalone: true, selector: "si-ai-message", inputs: { content: { classPropertyName: "content", publicName: "content", isSignal: true, isRequired: false, transformFunction: null }, contentFormatter: { classPropertyName: "contentFormatter", publicName: "contentFormatter", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, actions: { classPropertyName: "actions", publicName: "actions", isSignal: true, isRequired: false, transformFunction: null }, secondaryActions: { classPropertyName: "secondaryActions", publicName: "secondaryActions", isSignal: true, isRequired: false, transformFunction: null }, actionParam: { classPropertyName: "actionParam", publicName: "actionParam", isSignal: true, isRequired: false, transformFunction: null }, secondaryActionsLabel: { classPropertyName: "secondaryActionsLabel", publicName: "secondaryActionsLabel", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "formattedContent", first: true, predicate: ["formattedContent"], descendants: true, isSignal: true }], ngImport: i0, template: "<si-chat-message alignment=\"start\" actionsPosition=\"bottom\" [loading]=\"loading()\">\n @if (content()) {\n @let content = textContent();\n @if (content) {\n <span class=\"text-pre-wrap\">{{ content }}</span>\n } @else {\n <div #formattedContent> </div>\n }\n }\n\n @if ((actions()?.length ?? 0 > 0) || (secondaryActions()?.length ?? 0 > 0)) {\n <div class=\"d-flex gap-4 ai-message-actions\" siChatMessageAction>\n @for (action of actions(); track $index) {\n <button\n type=\"button\"\n class=\"btn btn-ghost btn-circle btn-sm\"\n [disabled]=\"action.disabled\"\n [attr.aria-label]=\"action.label | translate\"\n (click)=\"action.action(actionParam(), action)\"\n >\n <si-icon [icon]=\"action.icon\" />\n </button>\n }\n\n @if (secondaryActions()?.length ?? 0 > 0) {\n <button\n type=\"button\"\n class=\"btn btn-ghost btn-circle btn-sm\"\n [cdkMenuTriggerFor]=\"secondaryActionsMenu\"\n [attr.aria-label]=\"secondaryActionsLabel() | translate\"\n [attr.title]=\"secondaryActionsLabel() | translate\"\n >\n <si-icon icon=\"element-optionsVertical\" />\n </button>\n\n <ng-template #secondaryActionsMenu>\n <si-menu-factory [items]=\"secondaryActions()\" [actionParam]=\"actionParam()\" />\n </ng-template>\n }\n </div>\n }\n</si-chat-message>\n", styles: [":host{display:block}si-chat-message{--chat-message-bubble-bg: transparent;--chat-message-bubble-padding: 0;margin-block-end:-4px}.ai-message-actions{margin-block-start:8px}:host ::ng-deep si-loading-spinner{--loading-spinner-size: 1.5em}\n"], dependencies: [{ kind: "directive", type: CdkMenuTrigger, selector: "[cdkMenuTriggerFor]", inputs: ["cdkMenuTriggerFor", "cdkMenuPosition", "cdkMenuTriggerData"], outputs: ["cdkMenuOpened", "cdkMenuClosed"], exportAs: ["cdkMenuTriggerFor"] }, { kind: "component", type: SiChatMessageComponent, selector: "si-chat-message", inputs: ["loading", "alignment", "actionsPosition"] }, { kind: "component", type: SiIconComponent, selector: "si-icon", inputs: ["icon"] }, { kind: "component", type: SiMenuFactoryComponent, selector: "si-menu-factory", inputs: ["items", "actionParam"] }, { kind: "directive", type: SiChatMessageActionDirective, selector: "[siChatMessageAction]" }, { kind: "pipe", type: SiTranslatePipe, name: "translate" }] });
237
206
  }
238
207
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: SiAiMessageComponent, decorators: [{
239
208
  type: Component,
@@ -244,9 +213,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.6", ngImpor
244
213
  SiMenuFactoryComponent,
245
214
  SiChatMessageActionDirective,
246
215
  SiTranslatePipe
247
- ], host: {
248
- class: 'si-ai-message'
249
- }, template: "<si-chat-message alignment=\"start\" actionsPosition=\"bottom\" [loading]=\"loading()\">\n @if (content()) {\n @let content = textContent();\n @if (content) {\n <span class=\"text-pre-wrap\">{{ content }}</span>\n } @else {\n <div #formattedContent> </div>\n }\n }\n\n @if (actions().length > 0 || secondaryActions().length > 0) {\n <div class=\"d-flex gap-4 ai-message-actions\" siChatMessageAction>\n @for (action of actions(); track $index) {\n <button\n type=\"button\"\n class=\"btn btn-ghost btn-circle btn-sm\"\n [disabled]=\"action.disabled\"\n [attr.aria-label]=\"action.label | translate\"\n (click)=\"action.action(actionParam(), action)\"\n >\n <si-icon [icon]=\"action.icon\" />\n </button>\n }\n\n @if (secondaryActions().length > 0) {\n <button\n type=\"button\"\n class=\"btn btn-ghost btn-circle btn-sm\"\n [cdkMenuTriggerFor]=\"secondaryActionsMenu\"\n [attr.aria-label]=\"secondaryActionsLabel() | translate\"\n [attr.title]=\"secondaryActionsLabel() | translate\"\n >\n <si-icon icon=\"element-optionsVertical\" />\n </button>\n\n <ng-template #secondaryActionsMenu>\n <si-menu-factory [items]=\"secondaryActions()\" [actionParam]=\"actionParam()\" />\n </ng-template>\n }\n </div>\n }\n</si-chat-message>\n", styles: [":host{display:block}si-chat-message{--chat-message-bubble-bg: transparent;--chat-message-bubble-padding: 0;margin-block-end:-4px}.ai-message-actions{margin-block-start:8px}:host ::ng-deep si-loading-spinner{--loading-spinner-size: 1.5em}\n"] }]
216
+ ], template: "<si-chat-message alignment=\"start\" actionsPosition=\"bottom\" [loading]=\"loading()\">\n @if (content()) {\n @let content = textContent();\n @if (content) {\n <span class=\"text-pre-wrap\">{{ content }}</span>\n } @else {\n <div #formattedContent> </div>\n }\n }\n\n @if ((actions()?.length ?? 0 > 0) || (secondaryActions()?.length ?? 0 > 0)) {\n <div class=\"d-flex gap-4 ai-message-actions\" siChatMessageAction>\n @for (action of actions(); track $index) {\n <button\n type=\"button\"\n class=\"btn btn-ghost btn-circle btn-sm\"\n [disabled]=\"action.disabled\"\n [attr.aria-label]=\"action.label | translate\"\n (click)=\"action.action(actionParam(), action)\"\n >\n <si-icon [icon]=\"action.icon\" />\n </button>\n }\n\n @if (secondaryActions()?.length ?? 0 > 0) {\n <button\n type=\"button\"\n class=\"btn btn-ghost btn-circle btn-sm\"\n [cdkMenuTriggerFor]=\"secondaryActionsMenu\"\n [attr.aria-label]=\"secondaryActionsLabel() | translate\"\n [attr.title]=\"secondaryActionsLabel() | translate\"\n >\n <si-icon icon=\"element-optionsVertical\" />\n </button>\n\n <ng-template #secondaryActionsMenu>\n <si-menu-factory [items]=\"secondaryActions()\" [actionParam]=\"actionParam()\" />\n </ng-template>\n }\n </div>\n }\n</si-chat-message>\n", styles: [":host{display:block}si-chat-message{--chat-message-bubble-bg: transparent;--chat-message-bubble-padding: 0;margin-block-end:-4px}.ai-message-actions{margin-block-start:8px}:host ::ng-deep si-loading-spinner{--loading-spinner-size: 1.5em}\n"] }]
250
217
  }], ctorParameters: () => [] });
251
218
 
252
219
  /**
@@ -260,55 +227,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.6", ngImpor
260
227
  * preview and remove functionality. It's designed to work with chat message components
261
228
  * to show files that have been uploaded or shared in conversations.
262
229
  *
263
- * @remarks
264
230
  * This component provides:
265
- * - Automatic file type detection with appropriate icons
231
+ * - A list of pills showing each attachment's name and an icon
266
232
  * - Optional preview modal for attachments
267
233
  * - Optional remove functionality for editable messages
268
- * - Flexible alignment (start/end) to match message alignment
269
- * - Support for various file types (images, videos, audio, documents, archives)
270
234
  *
271
- * The component is typically used within {@link SiUserMessageComponent} or {@link SiAiMessageComponent}
272
- * to display uploaded files, but can also be used standalone.
273
- *
274
- * @example
275
- * Basic usage with attachments:
276
- * ```html
277
- * <si-attachment-list [attachments]="files" />
278
- * ```
279
- *
280
- * @example
281
- * With remove functionality and custom alignment:
282
- * ```typescript
283
- * import { Component } from '@angular/core';
284
- * import { SiAttachmentListComponent, Attachment } from '@siemens/element-ng/chat-messages';
285
- *
286
- * @Component({
287
- * selector: 'app-chat',
288
- * imports: [SiAttachmentListComponent],
289
- * template: `
290
- * <si-attachment-list
291
- * [attachments]="attachments"
292
- * [alignment]="'end'"
293
- * [removable]="true"
294
- * (remove)="handleRemove($event)"
295
- * />
296
- * `
297
- * })
298
- * export class ChatComponent {
299
- * attachments: Attachment[] = [
300
- * { id: '1', name: 'report.pdf' },
301
- * { id: '2', name: 'image.png', previewTemplate: this.imagePreview }
302
- * ];
303
- *
304
- * handleRemove(attachment: Attachment) {
305
- * this.attachments = this.attachments.filter(a => a !== attachment);
306
- * }
307
- * }
308
- * ```
235
+ * The component is included within {@link SiUserMessageComponent}, {@link SiAiMessageComponent} and {@link SiChatInputComponent} but can also be used inside custom chat messages with {@link SiChatMessageComponent}
309
236
  *
310
237
  * @see {@link SiUserMessageComponent} for user message display
311
238
  * @see {@link SiAiMessageComponent} for AI message display
239
+ * @see {@link SiChatMessageComponent} for custom chat message display
240
+ * @see {@link SiChatInputComponent} for chat input with attachment support
312
241
  * @see {@link Attachment} for attachment data structure
313
242
  *
314
243
  * @experimental
@@ -376,6 +305,209 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.6", ngImpor
376
305
  * Copyright (c) Siemens 2016 - 2025
377
306
  * SPDX-License-Identifier: MIT
378
307
  */
308
+ /**
309
+ * A declarative container component for displaying a chat interface with automatic scroll-to-bottom behavior.
310
+ *
311
+ * This component provides the layout and styling for a chat interface, managing scrolling behavior
312
+ * to keep the newest messages visible while respecting user scrolling actions. It automatically
313
+ * scrolls to the bottom when new content is added, unless the user has scrolled up to view older messages.
314
+ *
315
+ * Use via content projection:
316
+ * - Default content: Chat messages displayed in the scrollable messages container or something like an empty state.
317
+ * - `si-inline-notification` selector: Notification component displayed above the input area
318
+ * - `si-chat-input` or `[siChatContainerInput]` selector: Input controls for composing messages
319
+ *
320
+ * @see {@link SiChatInputComponent} for the chat input wrapper component
321
+ * @see {@link SiChatContainerInputDirective} for other input controls to slot in
322
+ * @see {@link SiAiMessageComponent} for AI messages to slot in
323
+ * @see {@link SiUserMessageComponent} for user messages (in AI chats) to slot in
324
+ * @see {@link SiChatMessageComponent} for the chat message wrapper component to slot in other messages
325
+ *
326
+ * @experimental
327
+ */
328
+ class SiChatContainerComponent {
329
+ messagesContainer = viewChild('messagesContainer');
330
+ platformId = inject(PLATFORM_ID);
331
+ isUserAtBottom = true;
332
+ scrollTimeout;
333
+ resizeObserver;
334
+ contentObserver;
335
+ /**
336
+ * The color variant to apply to the container.
337
+ * @defaultValue 'base-0'
338
+ */
339
+ colorVariant = input('base-0');
340
+ /**
341
+ * Disables automatic scrolling to the bottom when new content is added.
342
+ * @defaultValue false
343
+ */
344
+ noAutoScroll = input(false, {
345
+ transform: (value) => value === '' || value === true
346
+ });
347
+ constructor() {
348
+ effect(() => {
349
+ if (this.messagesContainer()) {
350
+ this.setupResizeObserver();
351
+ this.setupContentObserver();
352
+ }
353
+ });
354
+ }
355
+ ngAfterContentInit() {
356
+ this.scrollToBottom();
357
+ }
358
+ ngOnDestroy() {
359
+ if (this.scrollTimeout) {
360
+ clearTimeout(this.scrollTimeout);
361
+ }
362
+ if (this.resizeObserver) {
363
+ this.resizeObserver.disconnect();
364
+ }
365
+ if (this.contentObserver) {
366
+ this.contentObserver.disconnect();
367
+ }
368
+ }
369
+ scrollToBottom() {
370
+ if (this.noAutoScroll() || !this.isUserAtBottom) {
371
+ return;
372
+ }
373
+ const container = this.messagesContainer();
374
+ if (!container) {
375
+ return;
376
+ }
377
+ const element = container.nativeElement;
378
+ element.scrollTop = element.scrollHeight;
379
+ }
380
+ debouncedScrollToBottom() {
381
+ if (this.scrollTimeout) {
382
+ clearTimeout(this.scrollTimeout);
383
+ }
384
+ this.scrollTimeout = setTimeout(() => {
385
+ this.scrollToBottom();
386
+ }, 100);
387
+ }
388
+ setupResizeObserver() {
389
+ if (!isPlatformBrowser(this.platformId)) {
390
+ return;
391
+ }
392
+ const container = this.messagesContainer();
393
+ if (!container || this.resizeObserver) {
394
+ return;
395
+ }
396
+ this.resizeObserver = new ResizeObserver(() => {
397
+ this.debouncedScrollToBottom();
398
+ });
399
+ this.resizeObserver.observe(container.nativeElement);
400
+ }
401
+ setupContentObserver() {
402
+ if (!isPlatformBrowser(this.platformId)) {
403
+ return;
404
+ }
405
+ const container = this.messagesContainer();
406
+ if (!container || this.contentObserver) {
407
+ return;
408
+ }
409
+ this.contentObserver = new MutationObserver(() => {
410
+ this.debouncedScrollToBottom();
411
+ });
412
+ this.contentObserver.observe(container.nativeElement, {
413
+ childList: true,
414
+ subtree: true,
415
+ characterData: true
416
+ });
417
+ }
418
+ checkIfUserAtBottom() {
419
+ const container = this.messagesContainer();
420
+ if (!container) {
421
+ return;
422
+ }
423
+ const element = container.nativeElement;
424
+ const threshold = 100;
425
+ this.isUserAtBottom =
426
+ element.scrollHeight - element.scrollTop - element.clientHeight < threshold;
427
+ }
428
+ onScroll() {
429
+ this.checkIfUserAtBottom();
430
+ }
431
+ /**
432
+ * Focuses the messages container element.
433
+ */
434
+ focus() {
435
+ const container = this.messagesContainer();
436
+ container?.nativeElement.focus();
437
+ }
438
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: SiChatContainerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
439
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "20.0.6", type: SiChatContainerComponent, isStandalone: true, selector: "si-chat-container", inputs: { colorVariant: { classPropertyName: "colorVariant", publicName: "colorVariant", isSignal: true, isRequired: false, transformFunction: null }, noAutoScroll: { classPropertyName: "noAutoScroll", publicName: "noAutoScroll", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "colorVariant()" }, classAttribute: "d-flex si-layout-inner flex-grow-1 flex-column h-100 w-100" }, viewQueries: [{ propertyName: "messagesContainer", first: true, predicate: ["messagesContainer"], descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"chat-container d-flex flex-column flex-grow-1 px-6 px-md-9 py-6 w-100\">\n <div\n #messagesContainer\n class=\"messages-container d-flex flex-column flex-grow-1 w-100 align-self-center\"\n tabindex=\"0\"\n (scroll)=\"onScroll()\"\n >\n <ng-content />\n </div>\n\n <div class=\"chat-input-area py-3 pt-0 mt-6 d-flex flex-column align-items-center\">\n <ng-content select=\"si-inline-notification\" />\n <ng-content select=\"si-chat-input,[siChatContainerInput]\" />\n </div>\n</div>\n", styles: [":host{overflow-y:hidden;overflow-x:visible}:host.base-0{background-color:var(--element-base-0)}:host.base-1{background-color:var(--element-base-1)}.chat-container{min-block-size:0;overflow-y:hidden;overflow-x:visible}.chat-container.initialized{scroll-behavior:smooth}.messages-container{max-inline-size:720px;overflow-y:auto;overflow-x:visible;gap:40px}.messages-container ::ng-deep si-user-message:not(.last){margin-block-end:-40px}.chat-input-area{justify-self:flex-end;align-self:center;overflow:visible;inline-size:100%;max-inline-size:720px}::ng-deep .chat-input-area:empty:not(:has(si-inline-notification)),::ng-deep .chat-input-area:has([sichatcontainerinput]:empty):not(:has(si-inline-notification)){display:none!important}.chat-input-area:empty ::ng-deep>si-inline-notification,.chat-input-area:empty ::ng-deep *>si-inline-notification,.chat-input-area:has([sichatcontainerinput]:empty) ::ng-deep>si-inline-notification,.chat-input-area:has([sichatcontainerinput]:empty) ::ng-deep *>si-inline-notification{margin-block-end:0!important}.chat-container:has(.messages-container:not(:empty)) .messages-empty-state{display:none!important}:host-context(.si-container-xs,.si-container-sm) ::ng-deep si-inline-notification .alert,:host-context(.si-container-xs,.si-container-sm) ::ng-deep si-inline-notification .alert-link{font-size:.875rem}\n"] });
440
+ }
441
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: SiChatContainerComponent, decorators: [{
442
+ type: Component,
443
+ args: [{ selector: 'si-chat-container', host: {
444
+ class: 'd-flex si-layout-inner flex-grow-1 flex-column h-100 w-100',
445
+ '[class]': 'colorVariant()'
446
+ }, template: "<div class=\"chat-container d-flex flex-column flex-grow-1 px-6 px-md-9 py-6 w-100\">\n <div\n #messagesContainer\n class=\"messages-container d-flex flex-column flex-grow-1 w-100 align-self-center\"\n tabindex=\"0\"\n (scroll)=\"onScroll()\"\n >\n <ng-content />\n </div>\n\n <div class=\"chat-input-area py-3 pt-0 mt-6 d-flex flex-column align-items-center\">\n <ng-content select=\"si-inline-notification\" />\n <ng-content select=\"si-chat-input,[siChatContainerInput]\" />\n </div>\n</div>\n", styles: [":host{overflow-y:hidden;overflow-x:visible}:host.base-0{background-color:var(--element-base-0)}:host.base-1{background-color:var(--element-base-1)}.chat-container{min-block-size:0;overflow-y:hidden;overflow-x:visible}.chat-container.initialized{scroll-behavior:smooth}.messages-container{max-inline-size:720px;overflow-y:auto;overflow-x:visible;gap:40px}.messages-container ::ng-deep si-user-message:not(.last){margin-block-end:-40px}.chat-input-area{justify-self:flex-end;align-self:center;overflow:visible;inline-size:100%;max-inline-size:720px}::ng-deep .chat-input-area:empty:not(:has(si-inline-notification)),::ng-deep .chat-input-area:has([sichatcontainerinput]:empty):not(:has(si-inline-notification)){display:none!important}.chat-input-area:empty ::ng-deep>si-inline-notification,.chat-input-area:empty ::ng-deep *>si-inline-notification,.chat-input-area:has([sichatcontainerinput]:empty) ::ng-deep>si-inline-notification,.chat-input-area:has([sichatcontainerinput]:empty) ::ng-deep *>si-inline-notification{margin-block-end:0!important}.chat-container:has(.messages-container:not(:empty)) .messages-empty-state{display:none!important}:host-context(.si-container-xs,.si-container-sm) ::ng-deep si-inline-notification .alert,:host-context(.si-container-xs,.si-container-sm) ::ng-deep si-inline-notification .alert-link{font-size:.875rem}\n"] }]
447
+ }], ctorParameters: () => [] });
448
+
449
+ /**
450
+ * Copyright (c) Siemens 2016 - 2025
451
+ * SPDX-License-Identifier: MIT
452
+ */
453
+ /**
454
+ * A directive to mark elements as input controls within a {@link SiChatContainerComponent}.
455
+ *
456
+ * This directive is used to identify and style input elements that belong to
457
+ * the chat container component, typically applied to form inputs or textareas
458
+ * used for composing chat messages.
459
+ *
460
+ * @example
461
+ * ```html
462
+ * <si-chat-container>
463
+ * <si-chat-message>Hello!</si-chat-message>
464
+ * <si-chat-message>How are you?</si-chat-message>
465
+ *
466
+ * <input siChatContainerInput type="text" placeholder="Type a message..." />
467
+ * </si-chat-container>
468
+ * ```
469
+ *
470
+ * @see {@link SiChatContainerComponent} for the chat container wrapper component
471
+ *
472
+ * @experimental
473
+ */
474
+ class SiChatContainerInputDirective {
475
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: SiChatContainerInputDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
476
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.0.6", type: SiChatContainerInputDirective, isStandalone: true, selector: "[siChatContainerInput]", ngImport: i0 });
477
+ }
478
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: SiChatContainerInputDirective, decorators: [{
479
+ type: Directive,
480
+ args: [{
481
+ selector: '[siChatContainerInput]'
482
+ }]
483
+ }] });
484
+
485
+ /**
486
+ * Copyright (c) Siemens 2016 - 2025
487
+ * SPDX-License-Identifier: MIT
488
+ */
489
+ /**
490
+ * Chat input component for composing and sending messages in conversational interfaces.
491
+ *
492
+ * The chat input component provides a text area for users to compose messages,
493
+ * supporting text, attachments, and contextual actions. It appears as a textarea
494
+ * with buttons for adding attachments and sending messages, as well as an optional disclaimer.
495
+ *
496
+ * The component automatically handles:
497
+ * - Styling for chat input and actions.
498
+ * - Dynamic resizing of the textarea based on content.
499
+ * - Uploading of and displaying of attachments above the input area.
500
+ * - Displaying primary and secondary actions.
501
+ *
502
+ * Additionally to the inputs and outputs documented here, the component supports content projection via the following slots:
503
+ * - Default content: Custom action buttons to display inline, prefer using the `actions` input for buttons, can be used in addition.
504
+ * - `siChatInputDisclaimer` selector: Custom disclaimer content to display below the input area, prefer using the `disclaimer` input for simple text disclaimers.
505
+ *
506
+ * @see {@link SiAttachmentListComponent} for the base attachment component
507
+ * @see {@link SiChatInputDisclaimerDirective} to slot in custom disclaimer content
508
+ *
509
+ * @experimental
510
+ */
379
511
  class SiChatInputComponent {
380
512
  static idCounter = 0;
381
513
  textInput = viewChild('textInput');
@@ -673,7 +805,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.6", ngImpor
673
805
  * SPDX-License-Identifier: MIT
674
806
  */
675
807
  /**
676
- * Directive to mark content as chat input disclaimer.
808
+ * Directive to mark content as chat input disclaimer into {@link SiChatInputComponent}.
677
809
  * Apply this directive to content that should be slotted into the disclaimer area.
678
810
  *
679
811
  * @example
@@ -684,6 +816,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.6", ngImpor
684
816
  * </div>
685
817
  * </si-chat-input>
686
818
  * ```
819
+ *
820
+ * @see {@link SiChatInputComponent} for the chat input wrapper component
821
+ *
822
+ * @experimental
687
823
  */
688
824
  class SiChatInputDisclaimerDirective {
689
825
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: SiChatInputDisclaimerDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
@@ -701,64 +837,24 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.6", ngImpor
701
837
  * SPDX-License-Identifier: MIT
702
838
  */
703
839
  /**
704
- * User message component for displaying user input in conversational interfaces.
840
+ * User message component for displaying the user's messages in conversational interfaces.
705
841
  *
706
- * The user message component renders user-submitted content in chat interfaces,
842
+ * The user message component renders user-submitted content in (AI) chat interfaces,
707
843
  * supporting text, attachments, and contextual actions. It appears as a text bubble
708
844
  * aligned to the right side and supports markdown formatting for rich content.
709
- *
710
- * @remarks
711
- * This component is designed for use in:
712
- * - AI chat interfaces where user input needs to be displayed
713
- * - Peer-to-peer conversation interfaces
714
- * - Conversation histories or chat transcripts
845
+ * Can be used within {@link SiChatContainerComponent}.
715
846
  *
716
847
  * The component automatically handles:
717
- * - Rendering markdown content with syntax highlighting
848
+ * - Styling for user messages distinct from AI or generic chat messages
849
+ * - Option to render markdown content, provide via `contentFormatter` input with a markdown renderer function (e.g., from {@link getMarkdownRenderer})
718
850
  * - Displaying attachments above the message bubble
719
- * - Showing primary and secondary actions on hover (desktop) or tap (mobile)
720
- * - Proper alignment and styling for user messages
721
- *
722
- * @example
723
- * Basic usage with content only:
724
- * ```html
725
- * <si-user-message [content]="'Hello, how can I help you?'" />
726
- * ```
727
- *
728
- * @example
729
- * With actions and attachments:
730
- * ```typescript
731
- * import { Component } from '@angular/core';
732
- * import { SiUserMessageComponent } from '@siemens/element-ng/chat-messages';
733
- *
734
- * @Component({
735
- * selector: 'app-chat',
736
- * imports: [SiUserMessageComponent],
737
- * template: `
738
- * <si-user-message
739
- * [content]="message.text"
740
- * [actions]="messageActions"
741
- * [secondaryActions]="menuActions"
742
- * [attachments]="message.attachments"
743
- * [actionParam]="message"
744
- * />
745
- * `
746
- * })
747
- * export class ChatComponent {
748
- * messageActions = [
749
- * { icon: 'copy', label: 'Copy', action: (id) => this.copyMessage(id) },
750
- * { icon: 'edit', label: 'Edit', action: (id) => this.editMessage(id) }
751
- * ];
752
- *
753
- * menuActions = [
754
- * { label: 'Delete', action: (id) => this.deleteMessage(id) }
755
- * ];
756
- * }
757
- * ```
851
+ * - Displaying primary and secondary actions
758
852
  *
759
853
  * @see {@link SiChatMessageComponent} for the base message wrapper component
760
- * @see {@link SiAttachmentListComponent} for attachment handling
761
- * @see {@link SiMarkdownRendererComponent} for markdown rendering
854
+ * @see {@link SiAiMessageComponent} for the AI message component
855
+ * @see {@link SiAttachmentListComponent} for the base attachment component
856
+ * @see {@link getMarkdownRenderer} for markdown formatting support
857
+ * @see {@link SiChatContainerComponent} for the chat container to use this within
762
858
  *
763
859
  * @experimental
764
860
  */
@@ -809,7 +905,7 @@ class SiUserMessageComponent {
809
905
  * ```
810
906
  */
811
907
  secondaryActionsLabel = input(t(() => $localize `:@@SI_USER_MESSAGE.SECONDARY_ACTIONS:More actions`));
812
- hasAttachments = computed(() => this.attachments().length > 0);
908
+ hasAttachments = computed(() => this.attachments()?.length > 0);
813
909
  textContent = signal(undefined);
814
910
  constructor() {
815
911
  effect(() => {
@@ -835,7 +931,7 @@ class SiUserMessageComponent {
835
931
  });
836
932
  }
837
933
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: SiUserMessageComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
838
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.6", type: SiUserMessageComponent, isStandalone: true, selector: "si-user-message", inputs: { content: { classPropertyName: "content", publicName: "content", isSignal: true, isRequired: false, transformFunction: null }, contentFormatter: { classPropertyName: "contentFormatter", publicName: "contentFormatter", isSignal: true, isRequired: false, transformFunction: null }, actions: { classPropertyName: "actions", publicName: "actions", isSignal: true, isRequired: false, transformFunction: null }, secondaryActions: { classPropertyName: "secondaryActions", publicName: "secondaryActions", isSignal: true, isRequired: false, transformFunction: null }, attachments: { classPropertyName: "attachments", publicName: "attachments", isSignal: true, isRequired: false, transformFunction: null }, actionParam: { classPropertyName: "actionParam", publicName: "actionParam", isSignal: true, isRequired: false, transformFunction: null }, secondaryActionsLabel: { classPropertyName: "secondaryActionsLabel", publicName: "secondaryActionsLabel", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "formattedContent", first: true, predicate: ["formattedContent"], descendants: true, isSignal: true }], ngImport: i0, template: "<si-chat-message alignment=\"end\" actionsPosition=\"bottom\" [loading]=\"false\">\n @if (hasAttachments()) {\n <si-attachment-list alignment=\"end\" [attachments]=\"attachments()\" [removable]=\"false\" />\n }\n\n @if (content()) {\n @let content = textContent();\n @if (content) {\n <span class=\"text-pre-wrap\">{{ content }}</span>\n } @else {\n <div #formattedContent> </div>\n }\n }\n @if (actions().length > 0 || secondaryActions().length > 0) {\n <div class=\"d-flex gap-4 user-message-actions\" siChatMessageAction>\n @for (action of actions(); track $index) {\n <button\n type=\"button\"\n class=\"btn btn-ghost btn-circle btn-sm\"\n [disabled]=\"action.disabled\"\n [attr.aria-label]=\"action.label | translate\"\n (click)=\"action.action(actionParam(), action)\"\n >\n <si-icon [icon]=\"action.icon\" />\n </button>\n }\n\n @if (secondaryActions().length > 0) {\n <button\n type=\"button\"\n class=\"btn btn-ghost btn-circle btn-sm\"\n [cdkMenuTriggerFor]=\"secondaryActionsMenu\"\n [attr.aria-label]=\"secondaryActionsLabel() | translate\"\n [attr.title]=\"secondaryActionsLabel() | translate\"\n >\n <si-icon icon=\"element-optionsVertical\" />\n </button>\n\n <ng-template #secondaryActionsMenu>\n <si-menu-factory [items]=\"secondaryActions()\" [actionParam]=\"actionParam()\" />\n </ng-template>\n }\n </div>\n }\n</si-chat-message>\n", styles: [":host{display:block}:host:not(:has([siChatMessageAction])) si-chat-message{padding-block-end:36px!important}.user-message-actions{opacity:0;transition:opacity .2s ease}si-chat-message{--chat-message-bubble-bg: var(--element-base-input-experimental);max-inline-size:600px;margin-inline-start:auto}:host:hover .user-message-actions,.user-message-actions:hover,.user-message-actions:has(::ng-deep [aria-expanded=true]),:host-context(.si-container-xs,.si-container-sm) .user-message-actions:active{opacity:1}\n"], dependencies: [{ kind: "directive", type: CdkMenuTrigger, selector: "[cdkMenuTriggerFor]", inputs: ["cdkMenuTriggerFor", "cdkMenuPosition", "cdkMenuTriggerData"], outputs: ["cdkMenuOpened", "cdkMenuClosed"], exportAs: ["cdkMenuTriggerFor"] }, { kind: "component", type: SiAttachmentListComponent, selector: "si-attachment-list", inputs: ["attachments", "alignment", "removable", "removeLabel"], outputs: ["remove"] }, { kind: "component", type: SiChatMessageComponent, selector: "si-chat-message", inputs: ["loading", "alignment", "actionsPosition"] }, { kind: "component", type: SiIconComponent, selector: "si-icon", inputs: ["icon"] }, { kind: "component", type: SiMenuFactoryComponent, selector: "si-menu-factory", inputs: ["items", "actionParam"] }, { kind: "directive", type: SiChatMessageActionDirective, selector: "[siChatMessageAction]" }, { kind: "pipe", type: SiTranslatePipe, name: "translate" }] });
934
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.6", type: SiUserMessageComponent, isStandalone: true, selector: "si-user-message", inputs: { content: { classPropertyName: "content", publicName: "content", isSignal: true, isRequired: false, transformFunction: null }, contentFormatter: { classPropertyName: "contentFormatter", publicName: "contentFormatter", isSignal: true, isRequired: false, transformFunction: null }, actions: { classPropertyName: "actions", publicName: "actions", isSignal: true, isRequired: false, transformFunction: null }, secondaryActions: { classPropertyName: "secondaryActions", publicName: "secondaryActions", isSignal: true, isRequired: false, transformFunction: null }, attachments: { classPropertyName: "attachments", publicName: "attachments", isSignal: true, isRequired: false, transformFunction: null }, actionParam: { classPropertyName: "actionParam", publicName: "actionParam", isSignal: true, isRequired: false, transformFunction: null }, secondaryActionsLabel: { classPropertyName: "secondaryActionsLabel", publicName: "secondaryActionsLabel", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "formattedContent", first: true, predicate: ["formattedContent"], descendants: true, isSignal: true }], ngImport: i0, template: "<si-chat-message alignment=\"end\" actionsPosition=\"bottom\" [loading]=\"false\">\n @if (hasAttachments()) {\n <si-attachment-list alignment=\"end\" [attachments]=\"attachments()\" [removable]=\"false\" />\n }\n\n @if (content()) {\n @let content = textContent();\n @if (content) {\n <span class=\"text-pre-wrap\">{{ content }}</span>\n } @else {\n <div #formattedContent> </div>\n }\n }\n @if ((actions()?.length ?? 0 > 0) || (secondaryActions()?.length ?? 0 > 0)) {\n <div class=\"d-flex gap-4 user-message-actions\" siChatMessageAction>\n @for (action of actions(); track $index) {\n <button\n type=\"button\"\n class=\"btn btn-ghost btn-circle btn-sm\"\n [disabled]=\"action.disabled\"\n [attr.aria-label]=\"action.label | translate\"\n (click)=\"action.action(actionParam(), action)\"\n >\n <si-icon [icon]=\"action.icon\" />\n </button>\n }\n\n @if (secondaryActions()?.length ?? 0 > 0) {\n <button\n type=\"button\"\n class=\"btn btn-ghost btn-circle btn-sm\"\n [cdkMenuTriggerFor]=\"secondaryActionsMenu\"\n [attr.aria-label]=\"secondaryActionsLabel() | translate\"\n [attr.title]=\"secondaryActionsLabel() | translate\"\n >\n <si-icon icon=\"element-optionsVertical\" />\n </button>\n\n <ng-template #secondaryActionsMenu>\n <si-menu-factory [items]=\"secondaryActions()\" [actionParam]=\"actionParam()\" />\n </ng-template>\n }\n </div>\n }\n</si-chat-message>\n", styles: [":host{display:block}:host:not(:has([siChatMessageAction])) si-chat-message{padding-block-end:36px!important}.user-message-actions{opacity:0;transition:opacity .2s ease}si-chat-message{--chat-message-bubble-bg: var(--element-base-input-experimental);max-inline-size:600px;margin-inline-start:auto}:host:hover .user-message-actions,.user-message-actions:hover,.user-message-actions:has(::ng-deep [aria-expanded=true]),:host-context(.si-container-xs,.si-container-sm) .user-message-actions:active{opacity:1}\n"], dependencies: [{ kind: "directive", type: CdkMenuTrigger, selector: "[cdkMenuTriggerFor]", inputs: ["cdkMenuTriggerFor", "cdkMenuPosition", "cdkMenuTriggerData"], outputs: ["cdkMenuOpened", "cdkMenuClosed"], exportAs: ["cdkMenuTriggerFor"] }, { kind: "component", type: SiAttachmentListComponent, selector: "si-attachment-list", inputs: ["attachments", "alignment", "removable", "removeLabel"], outputs: ["remove"] }, { kind: "component", type: SiChatMessageComponent, selector: "si-chat-message", inputs: ["loading", "alignment", "actionsPosition"] }, { kind: "component", type: SiIconComponent, selector: "si-icon", inputs: ["icon"] }, { kind: "component", type: SiMenuFactoryComponent, selector: "si-menu-factory", inputs: ["items", "actionParam"] }, { kind: "directive", type: SiChatMessageActionDirective, selector: "[siChatMessageAction]" }, { kind: "pipe", type: SiTranslatePipe, name: "translate" }] });
839
935
  }
840
936
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.6", ngImport: i0, type: SiUserMessageComponent, decorators: [{
841
937
  type: Component,
@@ -847,7 +943,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.6", ngImpor
847
943
  SiMenuFactoryComponent,
848
944
  SiChatMessageActionDirective,
849
945
  SiTranslatePipe
850
- ], template: "<si-chat-message alignment=\"end\" actionsPosition=\"bottom\" [loading]=\"false\">\n @if (hasAttachments()) {\n <si-attachment-list alignment=\"end\" [attachments]=\"attachments()\" [removable]=\"false\" />\n }\n\n @if (content()) {\n @let content = textContent();\n @if (content) {\n <span class=\"text-pre-wrap\">{{ content }}</span>\n } @else {\n <div #formattedContent> </div>\n }\n }\n @if (actions().length > 0 || secondaryActions().length > 0) {\n <div class=\"d-flex gap-4 user-message-actions\" siChatMessageAction>\n @for (action of actions(); track $index) {\n <button\n type=\"button\"\n class=\"btn btn-ghost btn-circle btn-sm\"\n [disabled]=\"action.disabled\"\n [attr.aria-label]=\"action.label | translate\"\n (click)=\"action.action(actionParam(), action)\"\n >\n <si-icon [icon]=\"action.icon\" />\n </button>\n }\n\n @if (secondaryActions().length > 0) {\n <button\n type=\"button\"\n class=\"btn btn-ghost btn-circle btn-sm\"\n [cdkMenuTriggerFor]=\"secondaryActionsMenu\"\n [attr.aria-label]=\"secondaryActionsLabel() | translate\"\n [attr.title]=\"secondaryActionsLabel() | translate\"\n >\n <si-icon icon=\"element-optionsVertical\" />\n </button>\n\n <ng-template #secondaryActionsMenu>\n <si-menu-factory [items]=\"secondaryActions()\" [actionParam]=\"actionParam()\" />\n </ng-template>\n }\n </div>\n }\n</si-chat-message>\n", styles: [":host{display:block}:host:not(:has([siChatMessageAction])) si-chat-message{padding-block-end:36px!important}.user-message-actions{opacity:0;transition:opacity .2s ease}si-chat-message{--chat-message-bubble-bg: var(--element-base-input-experimental);max-inline-size:600px;margin-inline-start:auto}:host:hover .user-message-actions,.user-message-actions:hover,.user-message-actions:has(::ng-deep [aria-expanded=true]),:host-context(.si-container-xs,.si-container-sm) .user-message-actions:active{opacity:1}\n"] }]
946
+ ], template: "<si-chat-message alignment=\"end\" actionsPosition=\"bottom\" [loading]=\"false\">\n @if (hasAttachments()) {\n <si-attachment-list alignment=\"end\" [attachments]=\"attachments()\" [removable]=\"false\" />\n }\n\n @if (content()) {\n @let content = textContent();\n @if (content) {\n <span class=\"text-pre-wrap\">{{ content }}</span>\n } @else {\n <div #formattedContent> </div>\n }\n }\n @if ((actions()?.length ?? 0 > 0) || (secondaryActions()?.length ?? 0 > 0)) {\n <div class=\"d-flex gap-4 user-message-actions\" siChatMessageAction>\n @for (action of actions(); track $index) {\n <button\n type=\"button\"\n class=\"btn btn-ghost btn-circle btn-sm\"\n [disabled]=\"action.disabled\"\n [attr.aria-label]=\"action.label | translate\"\n (click)=\"action.action(actionParam(), action)\"\n >\n <si-icon [icon]=\"action.icon\" />\n </button>\n }\n\n @if (secondaryActions()?.length ?? 0 > 0) {\n <button\n type=\"button\"\n class=\"btn btn-ghost btn-circle btn-sm\"\n [cdkMenuTriggerFor]=\"secondaryActionsMenu\"\n [attr.aria-label]=\"secondaryActionsLabel() | translate\"\n [attr.title]=\"secondaryActionsLabel() | translate\"\n >\n <si-icon icon=\"element-optionsVertical\" />\n </button>\n\n <ng-template #secondaryActionsMenu>\n <si-menu-factory [items]=\"secondaryActions()\" [actionParam]=\"actionParam()\" />\n </ng-template>\n }\n </div>\n }\n</si-chat-message>\n", styles: [":host{display:block}:host:not(:has([siChatMessageAction])) si-chat-message{padding-block-end:36px!important}.user-message-actions{opacity:0;transition:opacity .2s ease}si-chat-message{--chat-message-bubble-bg: var(--element-base-input-experimental);max-inline-size:600px;margin-inline-start:auto}:host:hover .user-message-actions,.user-message-actions:hover,.user-message-actions:has(::ng-deep [aria-expanded=true]),:host-context(.si-container-xs,.si-container-sm) .user-message-actions:active{opacity:1}\n"] }]
851
947
  }], ctorParameters: () => [] });
852
948
 
853
949
  /**
@@ -859,5 +955,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.6", ngImpor
859
955
  * Generated bundle index. Do not edit.
860
956
  */
861
957
 
862
- export { SiAiMessageComponent, SiAttachmentListComponent, SiChatInputComponent, SiChatInputDisclaimerDirective, SiChatMessageActionDirective, SiChatMessageComponent, SiUserMessageComponent };
958
+ export { SiAiMessageComponent, SiAttachmentListComponent, SiChatContainerComponent, SiChatContainerInputDirective, SiChatInputComponent, SiChatInputDisclaimerDirective, SiChatMessageActionDirective, SiChatMessageComponent, SiUserMessageComponent };
863
959
  //# sourceMappingURL=siemens-element-ng-chat-messages.mjs.map