@ni/spright-components 6.20.4 → 6.21.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/all-components-bundle.js +364 -3
- package/dist/all-components-bundle.js.map +1 -1
- package/dist/all-components-bundle.min.js +3202 -3138
- package/dist/all-components-bundle.min.js.map +1 -1
- package/dist/custom-elements.json +51 -0
- package/dist/custom-elements.md +10 -6
- package/dist/esm/chat/conversation/index.d.ts +22 -0
- package/dist/esm/chat/conversation/index.js +47 -0
- package/dist/esm/chat/conversation/index.js.map +1 -1
- package/dist/esm/chat/conversation/models/auto-scroll-manager.d.ts +53 -0
- package/dist/esm/chat/conversation/models/auto-scroll-manager.js +222 -0
- package/dist/esm/chat/conversation/models/auto-scroll-manager.js.map +1 -0
- package/dist/esm/chat/conversation/styles.js +18 -1
- package/dist/esm/chat/conversation/styles.js.map +1 -1
- package/dist/esm/chat/conversation/template.js +9 -2
- package/dist/esm/chat/conversation/template.js.map +1 -1
- package/dist/esm/chat/conversation/testing/chat-conversation.pageobject.d.ts +55 -0
- package/dist/esm/chat/conversation/testing/chat-conversation.pageobject.js +194 -0
- package/dist/esm/chat/conversation/testing/chat-conversation.pageobject.js.map +1 -0
- package/dist/esm/chat/message/inbound/index.d.ts +3 -0
- package/dist/esm/chat/message/inbound/index.js +3 -0
- package/dist/esm/chat/message/inbound/index.js.map +1 -1
- package/dist/esm/chat/message/models/chat-message-internals.d.ts +38 -0
- package/dist/esm/chat/message/models/chat-message-internals.js +39 -0
- package/dist/esm/chat/message/models/chat-message-internals.js.map +1 -0
- package/dist/esm/chat/message/outbound/index.d.ts +3 -0
- package/dist/esm/chat/message/outbound/index.js +8 -0
- package/dist/esm/chat/message/outbound/index.js.map +1 -1
- package/dist/esm/chat/message/system/index.d.ts +3 -0
- package/dist/esm/chat/message/system/index.js +6 -0
- package/dist/esm/chat/message/system/index.js.map +1 -1
- package/dist/esm/chat/message/testing/chat-message.pageobject.d.ts +18 -0
- package/dist/esm/chat/message/testing/chat-message.pageobject.js +27 -0
- package/dist/esm/chat/message/testing/chat-message.pageobject.js.map +1 -0
- package/package.json +2 -2
|
@@ -63,6 +63,49 @@
|
|
|
63
63
|
"name": "appearance",
|
|
64
64
|
"privacy": "public"
|
|
65
65
|
},
|
|
66
|
+
{
|
|
67
|
+
"kind": "field",
|
|
68
|
+
"name": "autoScroll",
|
|
69
|
+
"type": {
|
|
70
|
+
"text": "boolean"
|
|
71
|
+
},
|
|
72
|
+
"privacy": "public",
|
|
73
|
+
"default": "false"
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
"kind": "method",
|
|
77
|
+
"name": "autoScrollChanged",
|
|
78
|
+
"privacy": "public",
|
|
79
|
+
"return": {
|
|
80
|
+
"type": {
|
|
81
|
+
"text": "void"
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
"kind": "method",
|
|
87
|
+
"name": "slottedHistoryMessagesChanged",
|
|
88
|
+
"privacy": "public",
|
|
89
|
+
"return": {
|
|
90
|
+
"type": {
|
|
91
|
+
"text": "void"
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
"parameters": [
|
|
95
|
+
{
|
|
96
|
+
"name": "_prev",
|
|
97
|
+
"type": {
|
|
98
|
+
"text": "HTMLElement[] | undefined"
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
"name": "next",
|
|
103
|
+
"type": {
|
|
104
|
+
"text": "HTMLElement[] | undefined"
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
]
|
|
108
|
+
},
|
|
66
109
|
{
|
|
67
110
|
"kind": "method",
|
|
68
111
|
"name": "slottedInputElementsChanged",
|
|
@@ -164,6 +207,14 @@
|
|
|
164
207
|
{
|
|
165
208
|
"name": "appearance",
|
|
166
209
|
"fieldName": "appearance"
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
"name": "auto-scroll",
|
|
213
|
+
"type": {
|
|
214
|
+
"text": "boolean"
|
|
215
|
+
},
|
|
216
|
+
"default": "false",
|
|
217
|
+
"fieldName": "autoScroll"
|
|
167
218
|
}
|
|
168
219
|
],
|
|
169
220
|
"superclass": {
|
package/dist/custom-elements.md
CHANGED
|
@@ -18,14 +18,17 @@
|
|
|
18
18
|
|
|
19
19
|
### Fields
|
|
20
20
|
|
|
21
|
-
| Name | Privacy | Type
|
|
22
|
-
| ------------ | ------- |
|
|
23
|
-
| `appearance` | public |
|
|
21
|
+
| Name | Privacy | Type | Default | Description | Inherited From |
|
|
22
|
+
| ------------ | ------- | --------- | ------- | ----------- | -------------- |
|
|
23
|
+
| `appearance` | public | | | | |
|
|
24
|
+
| `autoScroll` | public | `boolean` | `false` | | |
|
|
24
25
|
|
|
25
26
|
### Methods
|
|
26
27
|
|
|
27
28
|
| Name | Privacy | Description | Parameters | Return | Inherited From |
|
|
28
29
|
| ------------------------------- | ------- | ----------- | --------------------------------------------------------------------- | ------ | -------------- |
|
|
30
|
+
| `autoScrollChanged` | public | | | `void` | |
|
|
31
|
+
| `slottedHistoryMessagesChanged` | public | | `_prev: HTMLElement[] \| undefined, next: HTMLElement[] \| undefined` | `void` | |
|
|
29
32
|
| `slottedInputElementsChanged` | public | | `_prev: HTMLElement[] \| undefined, next: HTMLElement[] \| undefined` | `void` | |
|
|
30
33
|
| `slottedToolbarElementsChanged` | public | | `_prev: HTMLElement[] \| undefined, next: HTMLElement[] \| undefined` | `void` | |
|
|
31
34
|
| `slottedStartElementsChanged` | public | | `_prev: HTMLElement[] \| undefined, next: HTMLElement[] \| undefined` | `void` | |
|
|
@@ -33,9 +36,10 @@
|
|
|
33
36
|
|
|
34
37
|
### Attributes
|
|
35
38
|
|
|
36
|
-
| Name
|
|
37
|
-
|
|
|
38
|
-
| `appearance`
|
|
39
|
+
| Name | Field | Inherited From |
|
|
40
|
+
| ------------- | ---------- | -------------- |
|
|
41
|
+
| `appearance` | appearance | |
|
|
42
|
+
| `auto-scroll` | autoScroll | |
|
|
39
43
|
|
|
40
44
|
<hr/>
|
|
41
45
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { FoundationElement } from '@ni/fast-foundation';
|
|
2
|
+
import { AutoScrollManager } from './models/auto-scroll-manager';
|
|
2
3
|
declare global {
|
|
3
4
|
interface HTMLElementTagNameMap {
|
|
4
5
|
'spright-chat-conversation': ChatConversation;
|
|
@@ -9,6 +10,19 @@ declare global {
|
|
|
9
10
|
*/
|
|
10
11
|
export declare class ChatConversation extends FoundationElement {
|
|
11
12
|
appearance: undefined;
|
|
13
|
+
autoScroll: boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Manages auto-scroll behavior. Always present; its observers are registered
|
|
16
|
+
* while the conversation is connected and `autoScroll` is enabled.
|
|
17
|
+
* @internal
|
|
18
|
+
*/
|
|
19
|
+
readonly autoScrollManager: AutoScrollManager;
|
|
20
|
+
/** @internal */
|
|
21
|
+
readonly slottedMessages?: HTMLElement[];
|
|
22
|
+
/** @internal */
|
|
23
|
+
readonly slottedHistoryMessages?: HTMLElement[];
|
|
24
|
+
/** @internal */
|
|
25
|
+
historyEmpty: boolean;
|
|
12
26
|
/** @internal */
|
|
13
27
|
inputEmpty: boolean;
|
|
14
28
|
/** @internal */
|
|
@@ -25,6 +39,14 @@ export declare class ChatConversation extends FoundationElement {
|
|
|
25
39
|
endEmpty: boolean;
|
|
26
40
|
/** @internal */
|
|
27
41
|
readonly slottedEndElements?: HTMLElement[];
|
|
42
|
+
/** @internal */
|
|
43
|
+
messagesContainer: HTMLElement;
|
|
44
|
+
/** @internal */
|
|
45
|
+
anchoredContainer: HTMLElement;
|
|
46
|
+
connectedCallback(): void;
|
|
47
|
+
disconnectedCallback(): void;
|
|
48
|
+
autoScrollChanged(): void;
|
|
49
|
+
slottedHistoryMessagesChanged(_prev: HTMLElement[] | undefined, next: HTMLElement[] | undefined): void;
|
|
28
50
|
slottedInputElementsChanged(_prev: HTMLElement[] | undefined, next: HTMLElement[] | undefined): void;
|
|
29
51
|
slottedToolbarElementsChanged(_prev: HTMLElement[] | undefined, next: HTMLElement[] | undefined): void;
|
|
30
52
|
slottedStartElementsChanged(_prev: HTMLElement[] | undefined, next: HTMLElement[] | undefined): void;
|
|
@@ -4,6 +4,7 @@ import { attr, observable } from '@ni/fast-element';
|
|
|
4
4
|
import { styles } from './styles';
|
|
5
5
|
import { template } from './template';
|
|
6
6
|
import { ChatConversationAppearance } from './types';
|
|
7
|
+
import { AutoScrollManager } from './models/auto-scroll-manager';
|
|
7
8
|
/**
|
|
8
9
|
* A Spright component for displaying a series of chat messages
|
|
9
10
|
*/
|
|
@@ -11,6 +12,15 @@ export class ChatConversation extends FoundationElement {
|
|
|
11
12
|
constructor() {
|
|
12
13
|
super(...arguments);
|
|
13
14
|
this.appearance = ChatConversationAppearance.default;
|
|
15
|
+
this.autoScroll = false;
|
|
16
|
+
/**
|
|
17
|
+
* Manages auto-scroll behavior. Always present; its observers are registered
|
|
18
|
+
* while the conversation is connected and `autoScroll` is enabled.
|
|
19
|
+
* @internal
|
|
20
|
+
*/
|
|
21
|
+
this.autoScrollManager = new AutoScrollManager(this);
|
|
22
|
+
/** @internal */
|
|
23
|
+
this.historyEmpty = true;
|
|
14
24
|
/** @internal */
|
|
15
25
|
this.inputEmpty = true;
|
|
16
26
|
/** @internal */
|
|
@@ -20,6 +30,31 @@ export class ChatConversation extends FoundationElement {
|
|
|
20
30
|
/** @internal */
|
|
21
31
|
this.endEmpty = true;
|
|
22
32
|
}
|
|
33
|
+
connectedCallback() {
|
|
34
|
+
super.connectedCallback();
|
|
35
|
+
if (this.autoScroll) {
|
|
36
|
+
this.autoScrollManager.connect();
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
disconnectedCallback() {
|
|
40
|
+
super.disconnectedCallback();
|
|
41
|
+
if (this.autoScroll) {
|
|
42
|
+
this.autoScrollManager.disconnect();
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
autoScrollChanged() {
|
|
46
|
+
if (this.$fastController.isConnected) {
|
|
47
|
+
if (this.autoScroll) {
|
|
48
|
+
this.autoScrollManager.connect();
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
this.autoScrollManager.disconnect();
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
slottedHistoryMessagesChanged(_prev, next) {
|
|
56
|
+
this.historyEmpty = next === undefined || next.length === 0;
|
|
57
|
+
}
|
|
23
58
|
slottedInputElementsChanged(_prev, next) {
|
|
24
59
|
this.inputEmpty = next === undefined || next.length === 0;
|
|
25
60
|
}
|
|
@@ -36,6 +71,18 @@ export class ChatConversation extends FoundationElement {
|
|
|
36
71
|
__decorate([
|
|
37
72
|
attr
|
|
38
73
|
], ChatConversation.prototype, "appearance", void 0);
|
|
74
|
+
__decorate([
|
|
75
|
+
attr({ attribute: 'auto-scroll', mode: 'boolean' })
|
|
76
|
+
], ChatConversation.prototype, "autoScroll", void 0);
|
|
77
|
+
__decorate([
|
|
78
|
+
observable
|
|
79
|
+
], ChatConversation.prototype, "slottedMessages", void 0);
|
|
80
|
+
__decorate([
|
|
81
|
+
observable
|
|
82
|
+
], ChatConversation.prototype, "slottedHistoryMessages", void 0);
|
|
83
|
+
__decorate([
|
|
84
|
+
observable
|
|
85
|
+
], ChatConversation.prototype, "historyEmpty", void 0);
|
|
39
86
|
__decorate([
|
|
40
87
|
observable
|
|
41
88
|
], ChatConversation.prototype, "inputEmpty", void 0);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/chat/conversation/index.ts"],"names":[],"mappings":";AAAA,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACtE,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,0BAA0B,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/chat/conversation/index.ts"],"names":[],"mappings":"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","sourcesContent":["import { DesignSystem, FoundationElement } from '@ni/fast-foundation';\nimport { attr, observable } from '@ni/fast-element';\nimport { styles } from './styles';\nimport { template } from './template';\nimport { ChatConversationAppearance } from './types';\nimport { AutoScrollManager } from './models/auto-scroll-manager';\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'spright-chat-conversation': ChatConversation;\n }\n}\n\n/**\n * A Spright component for displaying a series of chat messages\n */\nexport class ChatConversation extends FoundationElement {\n @attr\n public appearance = ChatConversationAppearance.default;\n\n @attr({ attribute: 'auto-scroll', mode: 'boolean' })\n public autoScroll = false;\n\n /**\n * Manages auto-scroll behavior. Always present; its observers are registered\n * while the conversation is connected and `autoScroll` is enabled.\n * @internal\n */\n public readonly autoScrollManager = new AutoScrollManager(this);\n\n /** @internal */\n @observable\n public readonly slottedMessages?: HTMLElement[];\n\n /** @internal */\n @observable\n public readonly slottedHistoryMessages?: HTMLElement[];\n\n /** @internal */\n @observable\n public historyEmpty = true;\n\n /** @internal */\n @observable\n public inputEmpty = true;\n\n /** @internal */\n @observable\n public readonly slottedInputElements?: HTMLElement[];\n\n /** @internal */\n @observable\n public toolbarEmpty = true;\n\n /** @internal */\n @observable\n public readonly slottedToolbarElements?: HTMLElement[];\n\n /** @internal */\n @observable\n public startEmpty = true;\n\n /** @internal */\n @observable\n public readonly slottedStartElements?: HTMLElement[];\n\n /** @internal */\n @observable\n public endEmpty = true;\n\n /** @internal */\n @observable\n public readonly slottedEndElements?: HTMLElement[];\n\n /** @internal */\n public messagesContainer!: HTMLElement;\n\n /** @internal */\n public anchoredContainer!: HTMLElement;\n\n public override connectedCallback(): void {\n super.connectedCallback();\n if (this.autoScroll) {\n this.autoScrollManager.connect();\n }\n }\n\n public override disconnectedCallback(): void {\n super.disconnectedCallback();\n if (this.autoScroll) {\n this.autoScrollManager.disconnect();\n }\n }\n\n public autoScrollChanged(): void {\n if (this.$fastController.isConnected) {\n if (this.autoScroll) {\n this.autoScrollManager.connect();\n } else {\n this.autoScrollManager.disconnect();\n }\n }\n }\n\n public slottedHistoryMessagesChanged(\n _prev: HTMLElement[] | undefined,\n next: HTMLElement[] | undefined\n ): void {\n this.historyEmpty = next === undefined || next.length === 0;\n }\n\n public slottedInputElementsChanged(\n _prev: HTMLElement[] | undefined,\n next: HTMLElement[] | undefined\n ): void {\n this.inputEmpty = next === undefined || next.length === 0;\n }\n\n public slottedToolbarElementsChanged(\n _prev: HTMLElement[] | undefined,\n next: HTMLElement[] | undefined\n ): void {\n this.toolbarEmpty = next === undefined || next.length === 0;\n }\n\n public slottedStartElementsChanged(\n _prev: HTMLElement[] | undefined,\n next: HTMLElement[] | undefined\n ): void {\n this.startEmpty = next === undefined || next.length === 0;\n }\n\n public slottedEndElementsChanged(\n _prev: HTMLElement[] | undefined,\n next: HTMLElement[] | undefined\n ): void {\n this.endEmpty = next === undefined || next.length === 0;\n }\n}\n\nconst sprightChatConversation = ChatConversation.compose({\n baseName: 'chat-conversation',\n template,\n styles\n});\n\nDesignSystem.getOrCreate()\n .withPrefix('spright')\n .register(sprightChatConversation());\nexport const chatConversationTag = 'spright-chat-conversation';\n"]}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { type Subscriber } from '@ni/fast-element';
|
|
2
|
+
import type { ChatConversation } from '..';
|
|
3
|
+
/**
|
|
4
|
+
* Manages auto-scroll behavior for the chat conversation:
|
|
5
|
+
* - Splits messages into `default` and `history` slots so the top message of the
|
|
6
|
+
* `default` slot becomes the anchor: it is moved to the top of the viewport and scrolled to on insert
|
|
7
|
+
* - Implements auto scroll when at the bottom of the window as content is added until the user scrolls away
|
|
8
|
+
* @internal
|
|
9
|
+
*/
|
|
10
|
+
export declare class AutoScrollManager implements Subscriber {
|
|
11
|
+
private readonly conversation;
|
|
12
|
+
/**
|
|
13
|
+
* Whether auto-scroll is currently following new content. Set to false when
|
|
14
|
+
* the user scrolls away from the bottom and back to true when they return.
|
|
15
|
+
*/
|
|
16
|
+
autoScrollEngaged: boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Whether the anchored region is reserving a viewport of space
|
|
19
|
+
*/
|
|
20
|
+
anchorActive: boolean;
|
|
21
|
+
get isActive(): boolean;
|
|
22
|
+
private scrollAnchorMessage?;
|
|
23
|
+
private programmaticScrollTarget?;
|
|
24
|
+
private resizeObserver?;
|
|
25
|
+
private scrollUpdatePending;
|
|
26
|
+
private pendingAnchorInsert;
|
|
27
|
+
private previousMessages;
|
|
28
|
+
private readonly conversationNotifier;
|
|
29
|
+
constructor(conversation: ChatConversation);
|
|
30
|
+
connect(): void;
|
|
31
|
+
disconnect(): void;
|
|
32
|
+
handleChange(source: unknown, args: unknown): void;
|
|
33
|
+
private onMessagesChanged;
|
|
34
|
+
private repartition;
|
|
35
|
+
private clearSlotAssignments;
|
|
36
|
+
private scheduleScrollUpdate;
|
|
37
|
+
/**
|
|
38
|
+
* Pins the most recently inserted anchor message near the top of the
|
|
39
|
+
* viewport.
|
|
40
|
+
*/
|
|
41
|
+
private anchorToLastInsertedMessage;
|
|
42
|
+
private followContent;
|
|
43
|
+
private onContentSizeChanged;
|
|
44
|
+
private readonly onScroll;
|
|
45
|
+
private getDistanceFromBottom;
|
|
46
|
+
private getMaxScrollTop;
|
|
47
|
+
private smoothScrollTo;
|
|
48
|
+
private instantScrollTo;
|
|
49
|
+
private setScrollAnchorMessage;
|
|
50
|
+
private getLastAnchorMessage;
|
|
51
|
+
private findLatestAnchorIndex;
|
|
52
|
+
private getOrderedMessages;
|
|
53
|
+
}
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import { __decorate } from "tslib";
|
|
2
|
+
import { Observable, observable } from '@ni/fast-element';
|
|
3
|
+
import { ChatMessageInternals } from '../../message/models/chat-message-internals';
|
|
4
|
+
// Distance from the bottom (px) within which the conversation is considered "at the bottom".
|
|
5
|
+
const scrollingPixelThreshold = 10;
|
|
6
|
+
// Slot name for messages that precede the current turn's anchor message.
|
|
7
|
+
const historySlotName = 'history';
|
|
8
|
+
/**
|
|
9
|
+
* Manages auto-scroll behavior for the chat conversation:
|
|
10
|
+
* - Splits messages into `default` and `history` slots so the top message of the
|
|
11
|
+
* `default` slot becomes the anchor: it is moved to the top of the viewport and scrolled to on insert
|
|
12
|
+
* - Implements auto scroll when at the bottom of the window as content is added until the user scrolls away
|
|
13
|
+
* @internal
|
|
14
|
+
*/
|
|
15
|
+
export class AutoScrollManager {
|
|
16
|
+
get isActive() {
|
|
17
|
+
return this.resizeObserver !== undefined;
|
|
18
|
+
}
|
|
19
|
+
constructor(conversation) {
|
|
20
|
+
this.conversation = conversation;
|
|
21
|
+
/**
|
|
22
|
+
* Whether auto-scroll is currently following new content. Set to false when
|
|
23
|
+
* the user scrolls away from the bottom and back to true when they return.
|
|
24
|
+
*/
|
|
25
|
+
this.autoScrollEngaged = true;
|
|
26
|
+
/**
|
|
27
|
+
* Whether the anchored region is reserving a viewport of space
|
|
28
|
+
*/
|
|
29
|
+
this.anchorActive = false;
|
|
30
|
+
this.scrollUpdatePending = false;
|
|
31
|
+
this.pendingAnchorInsert = false;
|
|
32
|
+
this.previousMessages = [];
|
|
33
|
+
this.onScroll = () => {
|
|
34
|
+
const container = this.conversation.messagesContainer;
|
|
35
|
+
if (this.programmaticScrollTarget !== undefined) {
|
|
36
|
+
// The programmatic scroll always targets the bottom, so treat it as
|
|
37
|
+
// settled once we reach that target or the bottom itself.
|
|
38
|
+
const reachedTarget = Math.abs(container.scrollTop - this.programmaticScrollTarget) <= 1;
|
|
39
|
+
const reachedBottom = this.getDistanceFromBottom() <= scrollingPixelThreshold;
|
|
40
|
+
if (reachedTarget || reachedBottom) {
|
|
41
|
+
this.programmaticScrollTarget = undefined;
|
|
42
|
+
}
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
this.autoScrollEngaged = this.getDistanceFromBottom() <= scrollingPixelThreshold;
|
|
46
|
+
};
|
|
47
|
+
this.conversationNotifier = Observable.getNotifier(this.conversation);
|
|
48
|
+
}
|
|
49
|
+
connect() {
|
|
50
|
+
this.autoScrollEngaged = true;
|
|
51
|
+
this.previousMessages = this.getOrderedMessages();
|
|
52
|
+
this.conversationNotifier.subscribe(this, 'slottedMessages');
|
|
53
|
+
this.conversationNotifier.subscribe(this, 'slottedHistoryMessages');
|
|
54
|
+
this.conversation.messagesContainer.addEventListener('scroll', this.onScroll, { passive: true });
|
|
55
|
+
this.resizeObserver = new ResizeObserver(() => {
|
|
56
|
+
this.onContentSizeChanged();
|
|
57
|
+
});
|
|
58
|
+
// Observe the anchored region for streamed content growth and the scroll
|
|
59
|
+
// viewport so the conversation stays pinned when its height changes.
|
|
60
|
+
this.resizeObserver.observe(this.conversation.anchoredContainer);
|
|
61
|
+
this.resizeObserver.observe(this.conversation.messagesContainer);
|
|
62
|
+
this.repartition(this.previousMessages);
|
|
63
|
+
}
|
|
64
|
+
disconnect() {
|
|
65
|
+
this.conversationNotifier.unsubscribe(this, 'slottedMessages');
|
|
66
|
+
this.conversationNotifier.unsubscribe(this, 'slottedHistoryMessages');
|
|
67
|
+
this.conversation.messagesContainer.removeEventListener('scroll', this.onScroll);
|
|
68
|
+
this.resizeObserver?.disconnect();
|
|
69
|
+
this.resizeObserver = undefined;
|
|
70
|
+
this.setScrollAnchorMessage(undefined);
|
|
71
|
+
this.clearSlotAssignments();
|
|
72
|
+
this.anchorActive = false;
|
|
73
|
+
this.previousMessages = [];
|
|
74
|
+
}
|
|
75
|
+
handleChange(source, args) {
|
|
76
|
+
if (source === this.conversation
|
|
77
|
+
&& (args === 'slottedMessages' || args === 'slottedHistoryMessages')) {
|
|
78
|
+
this.onMessagesChanged();
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
onMessagesChanged() {
|
|
82
|
+
const current = this.getOrderedMessages();
|
|
83
|
+
const previousSet = new Set(this.previousMessages);
|
|
84
|
+
const addedMessages = current.filter(message => !previousSet.has(message));
|
|
85
|
+
this.previousMessages = current;
|
|
86
|
+
this.repartition(current);
|
|
87
|
+
if (addedMessages.length === 0) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
const hasAnchorMessage = addedMessages.some(message => message.messageInternals.anchorOnInsert);
|
|
91
|
+
this.scheduleScrollUpdate(hasAnchorMessage);
|
|
92
|
+
}
|
|
93
|
+
repartition(messages) {
|
|
94
|
+
const anchorIndex = this.findLatestAnchorIndex(messages);
|
|
95
|
+
messages.forEach((message, index) => {
|
|
96
|
+
message.messageInternals.slot = anchorIndex >= 0 && index < anchorIndex
|
|
97
|
+
? historySlotName
|
|
98
|
+
: undefined;
|
|
99
|
+
});
|
|
100
|
+
this.anchorActive = anchorIndex >= 0;
|
|
101
|
+
}
|
|
102
|
+
clearSlotAssignments() {
|
|
103
|
+
for (const message of this.getOrderedMessages()) {
|
|
104
|
+
message.messageInternals.slot = undefined;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
scheduleScrollUpdate(hasAnchorMessage) {
|
|
108
|
+
this.pendingAnchorInsert = this.pendingAnchorInsert || hasAnchorMessage;
|
|
109
|
+
if (this.scrollUpdatePending) {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
this.scrollUpdatePending = true;
|
|
113
|
+
requestAnimationFrame(() => {
|
|
114
|
+
this.scrollUpdatePending = false;
|
|
115
|
+
const anchorInsert = this.pendingAnchorInsert;
|
|
116
|
+
this.pendingAnchorInsert = false;
|
|
117
|
+
if (anchorInsert) {
|
|
118
|
+
this.anchorToLastInsertedMessage();
|
|
119
|
+
}
|
|
120
|
+
else if (this.autoScrollEngaged) {
|
|
121
|
+
this.followContent();
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Pins the most recently inserted anchor message near the top of the
|
|
127
|
+
* viewport.
|
|
128
|
+
*/
|
|
129
|
+
anchorToLastInsertedMessage() {
|
|
130
|
+
const message = this.getLastAnchorMessage();
|
|
131
|
+
if (message === undefined) {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
this.setScrollAnchorMessage(message);
|
|
135
|
+
this.autoScrollEngaged = true;
|
|
136
|
+
this.smoothScrollTo(this.getMaxScrollTop());
|
|
137
|
+
}
|
|
138
|
+
followContent() {
|
|
139
|
+
this.instantScrollTo(this.getMaxScrollTop());
|
|
140
|
+
}
|
|
141
|
+
onContentSizeChanged() {
|
|
142
|
+
// Reacts to streamed content growth and viewport height changes.
|
|
143
|
+
// While a pending or in-progress anchor insert owns positioning, let its
|
|
144
|
+
// smooth scroll settle instead of competing with an instant follow.
|
|
145
|
+
if (this.pendingAnchorInsert
|
|
146
|
+
|| this.programmaticScrollTarget !== undefined) {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
if (this.autoScrollEngaged) {
|
|
150
|
+
this.followContent();
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
getDistanceFromBottom() {
|
|
154
|
+
const { scrollTop, scrollHeight, clientHeight } = this.conversation.messagesContainer;
|
|
155
|
+
return scrollHeight - scrollTop - clientHeight;
|
|
156
|
+
}
|
|
157
|
+
getMaxScrollTop() {
|
|
158
|
+
const { scrollHeight, clientHeight } = this.conversation.messagesContainer;
|
|
159
|
+
return Math.max(0, scrollHeight - clientHeight);
|
|
160
|
+
}
|
|
161
|
+
smoothScrollTo(scrollTop) {
|
|
162
|
+
const container = this.conversation.messagesContainer;
|
|
163
|
+
if (Math.abs(container.scrollTop - scrollTop) <= 1) {
|
|
164
|
+
// No movement is needed, so `scrollTo` would not emit a scroll event
|
|
165
|
+
// to clear the programmatic guard. Snap to the exact target and
|
|
166
|
+
// leave the guard clear so streamed content keeps being followed.
|
|
167
|
+
this.programmaticScrollTarget = undefined;
|
|
168
|
+
container.scrollTop = scrollTop;
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
this.programmaticScrollTarget = scrollTop;
|
|
172
|
+
container.scrollTo({
|
|
173
|
+
top: scrollTop,
|
|
174
|
+
behavior: 'smooth'
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
instantScrollTo(scrollTop) {
|
|
178
|
+
this.conversation.messagesContainer.scrollTop = scrollTop;
|
|
179
|
+
}
|
|
180
|
+
setScrollAnchorMessage(message) {
|
|
181
|
+
if (this.scrollAnchorMessage === message) {
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
if (this.scrollAnchorMessage !== undefined) {
|
|
185
|
+
this.scrollAnchorMessage.messageInternals.isScrollAnchor = false;
|
|
186
|
+
}
|
|
187
|
+
this.scrollAnchorMessage = message;
|
|
188
|
+
if (message !== undefined) {
|
|
189
|
+
message.messageInternals.isScrollAnchor = true;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
getLastAnchorMessage() {
|
|
193
|
+
const messages = this.getOrderedMessages();
|
|
194
|
+
const index = this.findLatestAnchorIndex(messages);
|
|
195
|
+
return index >= 0 ? messages[index] : undefined;
|
|
196
|
+
}
|
|
197
|
+
findLatestAnchorIndex(messages) {
|
|
198
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
199
|
+
const message = messages[i];
|
|
200
|
+
if (message?.messageInternals.anchorOnInsert) {
|
|
201
|
+
return i;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return -1;
|
|
205
|
+
}
|
|
206
|
+
getOrderedMessages() {
|
|
207
|
+
const messages = [];
|
|
208
|
+
for (const child of Array.from(this.conversation.children)) {
|
|
209
|
+
if (ChatMessageInternals.elementHasMessageInternals(child)) {
|
|
210
|
+
messages.push(child);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return messages;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
__decorate([
|
|
217
|
+
observable
|
|
218
|
+
], AutoScrollManager.prototype, "autoScrollEngaged", void 0);
|
|
219
|
+
__decorate([
|
|
220
|
+
observable
|
|
221
|
+
], AutoScrollManager.prototype, "anchorActive", void 0);
|
|
222
|
+
//# sourceMappingURL=auto-scroll-manager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auto-scroll-manager.js","sourceRoot":"","sources":["../../../../../src/chat/conversation/models/auto-scroll-manager.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n type Notifier,\n Observable,\n observable,\n type Subscriber\n} from '@ni/fast-element';\nimport type { ChatConversation } from '..';\nimport {\n ChatMessageInternals,\n type ChatMessage\n} from '../../message/models/chat-message-internals';\n\n// Distance from the bottom (px) within which the conversation is considered \"at the bottom\".\nconst scrollingPixelThreshold = 10;\n\n// Slot name for messages that precede the current turn's anchor message.\nconst historySlotName = 'history';\n\n/**\n * Manages auto-scroll behavior for the chat conversation:\n * - Splits messages into `default` and `history` slots so the top message of the\n * `default` slot becomes the anchor: it is moved to the top of the viewport and scrolled to on insert\n * - Implements auto scroll when at the bottom of the window as content is added until the user scrolls away\n * @internal\n */\nexport class AutoScrollManager implements Subscriber {\n /**\n * Whether auto-scroll is currently following new content. Set to false when\n * the user scrolls away from the bottom and back to true when they return.\n */\n @observable\n public autoScrollEngaged = true;\n\n /**\n * Whether the anchored region is reserving a viewport of space\n */\n @observable\n public anchorActive = false;\n\n public get isActive(): boolean {\n return this.resizeObserver !== undefined;\n }\n\n private scrollAnchorMessage?: ChatMessage;\n private programmaticScrollTarget?: number;\n private resizeObserver?: ResizeObserver;\n private scrollUpdatePending = false;\n private pendingAnchorInsert = false;\n private previousMessages: ChatMessage[] = [];\n private readonly conversationNotifier: Notifier;\n\n public constructor(private readonly conversation: ChatConversation) {\n this.conversationNotifier = Observable.getNotifier(this.conversation);\n }\n\n public connect(): void {\n this.autoScrollEngaged = true;\n this.previousMessages = this.getOrderedMessages();\n this.conversationNotifier.subscribe(this, 'slottedMessages');\n this.conversationNotifier.subscribe(this, 'slottedHistoryMessages');\n this.conversation.messagesContainer.addEventListener(\n 'scroll',\n this.onScroll,\n { passive: true }\n );\n this.resizeObserver = new ResizeObserver(() => {\n this.onContentSizeChanged();\n });\n // Observe the anchored region for streamed content growth and the scroll\n // viewport so the conversation stays pinned when its height changes.\n this.resizeObserver.observe(this.conversation.anchoredContainer);\n this.resizeObserver.observe(this.conversation.messagesContainer);\n this.repartition(this.previousMessages);\n }\n\n public disconnect(): void {\n this.conversationNotifier.unsubscribe(this, 'slottedMessages');\n this.conversationNotifier.unsubscribe(this, 'slottedHistoryMessages');\n this.conversation.messagesContainer.removeEventListener(\n 'scroll',\n this.onScroll\n );\n this.resizeObserver?.disconnect();\n this.resizeObserver = undefined;\n this.setScrollAnchorMessage(undefined);\n this.clearSlotAssignments();\n this.anchorActive = false;\n this.previousMessages = [];\n }\n\n public handleChange(source: unknown, args: unknown): void {\n if (\n source === this.conversation\n && (args === 'slottedMessages' || args === 'slottedHistoryMessages')\n ) {\n this.onMessagesChanged();\n }\n }\n\n private onMessagesChanged(): void {\n const current = this.getOrderedMessages();\n const previousSet = new Set(this.previousMessages);\n const addedMessages = current.filter(\n message => !previousSet.has(message)\n );\n this.previousMessages = current;\n this.repartition(current);\n if (addedMessages.length === 0) {\n return;\n }\n const hasAnchorMessage = addedMessages.some(\n message => message.messageInternals.anchorOnInsert\n );\n this.scheduleScrollUpdate(hasAnchorMessage);\n }\n\n private repartition(messages: ChatMessage[]): void {\n const anchorIndex = this.findLatestAnchorIndex(messages);\n messages.forEach((message, index) => {\n message.messageInternals.slot = anchorIndex >= 0 && index < anchorIndex\n ? historySlotName\n : undefined;\n });\n this.anchorActive = anchorIndex >= 0;\n }\n\n private clearSlotAssignments(): void {\n for (const message of this.getOrderedMessages()) {\n message.messageInternals.slot = undefined;\n }\n }\n\n private scheduleScrollUpdate(hasAnchorMessage: boolean): void {\n this.pendingAnchorInsert = this.pendingAnchorInsert || hasAnchorMessage;\n if (this.scrollUpdatePending) {\n return;\n }\n this.scrollUpdatePending = true;\n requestAnimationFrame(() => {\n this.scrollUpdatePending = false;\n const anchorInsert = this.pendingAnchorInsert;\n this.pendingAnchorInsert = false;\n if (anchorInsert) {\n this.anchorToLastInsertedMessage();\n } else if (this.autoScrollEngaged) {\n this.followContent();\n }\n });\n }\n\n /**\n * Pins the most recently inserted anchor message near the top of the\n * viewport.\n */\n private anchorToLastInsertedMessage(): void {\n const message = this.getLastAnchorMessage();\n if (message === undefined) {\n return;\n }\n this.setScrollAnchorMessage(message);\n this.autoScrollEngaged = true;\n this.smoothScrollTo(this.getMaxScrollTop());\n }\n\n private followContent(): void {\n this.instantScrollTo(this.getMaxScrollTop());\n }\n\n private onContentSizeChanged(): void {\n // Reacts to streamed content growth and viewport height changes.\n // While a pending or in-progress anchor insert owns positioning, let its\n // smooth scroll settle instead of competing with an instant follow.\n if (\n this.pendingAnchorInsert\n || this.programmaticScrollTarget !== undefined\n ) {\n return;\n }\n if (this.autoScrollEngaged) {\n this.followContent();\n }\n }\n\n private readonly onScroll = (): void => {\n const container = this.conversation.messagesContainer;\n if (this.programmaticScrollTarget !== undefined) {\n // The programmatic scroll always targets the bottom, so treat it as\n // settled once we reach that target or the bottom itself.\n const reachedTarget = Math.abs(container.scrollTop - this.programmaticScrollTarget) <= 1;\n const reachedBottom = this.getDistanceFromBottom() <= scrollingPixelThreshold;\n if (reachedTarget || reachedBottom) {\n this.programmaticScrollTarget = undefined;\n }\n return;\n }\n this.autoScrollEngaged = this.getDistanceFromBottom() <= scrollingPixelThreshold;\n };\n\n private getDistanceFromBottom(): number {\n const { scrollTop, scrollHeight, clientHeight } = this.conversation.messagesContainer;\n return scrollHeight - scrollTop - clientHeight;\n }\n\n private getMaxScrollTop(): number {\n const { scrollHeight, clientHeight } = this.conversation.messagesContainer;\n return Math.max(0, scrollHeight - clientHeight);\n }\n\n private smoothScrollTo(scrollTop: number): void {\n const container = this.conversation.messagesContainer;\n if (Math.abs(container.scrollTop - scrollTop) <= 1) {\n // No movement is needed, so `scrollTo` would not emit a scroll event\n // to clear the programmatic guard. Snap to the exact target and\n // leave the guard clear so streamed content keeps being followed.\n this.programmaticScrollTarget = undefined;\n container.scrollTop = scrollTop;\n return;\n }\n this.programmaticScrollTarget = scrollTop;\n container.scrollTo({\n top: scrollTop,\n behavior: 'smooth'\n });\n }\n\n private instantScrollTo(scrollTop: number): void {\n this.conversation.messagesContainer.scrollTop = scrollTop;\n }\n\n private setScrollAnchorMessage(message?: ChatMessage): void {\n if (this.scrollAnchorMessage === message) {\n return;\n }\n if (this.scrollAnchorMessage !== undefined) {\n this.scrollAnchorMessage.messageInternals.isScrollAnchor = false;\n }\n this.scrollAnchorMessage = message;\n if (message !== undefined) {\n message.messageInternals.isScrollAnchor = true;\n }\n }\n\n private getLastAnchorMessage(): ChatMessage | undefined {\n const messages = this.getOrderedMessages();\n const index = this.findLatestAnchorIndex(messages);\n return index >= 0 ? messages[index] : undefined;\n }\n\n private findLatestAnchorIndex(messages: ChatMessage[]): number {\n for (let i = messages.length - 1; i >= 0; i--) {\n const message = messages[i];\n if (message?.messageInternals.anchorOnInsert) {\n return i;\n }\n }\n return -1;\n }\n\n private getOrderedMessages(): ChatMessage[] {\n const messages: ChatMessage[] = [];\n for (const child of Array.from(this.conversation.children)) {\n if (ChatMessageInternals.elementHasMessageInternals(child)) {\n messages.push(child);\n }\n }\n return messages;\n }\n}\n"]}
|
|
@@ -5,6 +5,7 @@ export const styles = css `
|
|
|
5
5
|
${display('flex')}
|
|
6
6
|
|
|
7
7
|
:host {
|
|
8
|
+
height: 480px;
|
|
8
9
|
flex-direction: column;
|
|
9
10
|
background: ${applicationBackgroundColor};
|
|
10
11
|
}
|
|
@@ -52,12 +53,28 @@ export const styles = css `
|
|
|
52
53
|
flex: 1;
|
|
53
54
|
display: flex;
|
|
54
55
|
flex-direction: column;
|
|
55
|
-
justify-content: flex-start;
|
|
56
56
|
row-gap: 32px;
|
|
57
57
|
padding: ${mediumPadding} ${standardPadding} ${mediumPadding}
|
|
58
58
|
${standardPadding};
|
|
59
59
|
background: ${sectionBackgroundImage};
|
|
60
60
|
overflow-y: auto;
|
|
61
|
+
overflow-anchor: none;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.messages-history,
|
|
65
|
+
.messages-anchored {
|
|
66
|
+
flex: none;
|
|
67
|
+
display: flex;
|
|
68
|
+
flex-direction: column;
|
|
69
|
+
row-gap: 32px;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.messages-history.region-empty {
|
|
73
|
+
display: none;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.messages-anchored.anchor-active {
|
|
77
|
+
min-height: 100%;
|
|
61
78
|
}
|
|
62
79
|
|
|
63
80
|
:host([appearance='overlay']) .messages {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"styles.js","sourceRoot":"","sources":["../../../../src/chat/conversation/styles.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,kBAAkB,CAAC;AACvC,OAAO,EACH,0BAA0B,EAC1B,WAAW,EACX,sBAAsB,EACtB,aAAa,EACb,sBAAsB,EACtB,aAAa,EACb,eAAe,EACf,oBAAoB,EACpB,QAAQ,EACR,aAAa,GAChB,MAAM,6DAA6D,CAAC;AACrE,OAAO,EAAE,OAAO,EAAE,MAAM,+BAA+B,CAAC;AAExD,MAAM,CAAC,MAAM,MAAM,GAAG,GAAG,CAAA;MACnB,OAAO,CAAC,MAAM,CAAC
|
|
1
|
+
{"version":3,"file":"styles.js","sourceRoot":"","sources":["../../../../src/chat/conversation/styles.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,kBAAkB,CAAC;AACvC,OAAO,EACH,0BAA0B,EAC1B,WAAW,EACX,sBAAsB,EACtB,aAAa,EACb,sBAAsB,EACtB,aAAa,EACb,eAAe,EACf,oBAAoB,EACpB,QAAQ,EACR,aAAa,GAChB,MAAM,6DAA6D,CAAC;AACrE,OAAO,EAAE,OAAO,EAAE,MAAM,+BAA+B,CAAC;AAExD,MAAM,CAAC,MAAM,MAAM,GAAG,GAAG,CAAA;MACnB,OAAO,CAAC,MAAM,CAAC;;;;;sBAKC,0BAA0B;;;;;;;;;;;;;;;;;;;;;;;;;;;uBA2BzB,eAAe;sBAChB,WAAW,UAAU,sBAAsB;;;;;;;;gBAQjD,aAAa;iBACZ,oBAAoB;UAC3B,QAAQ,CAAC,iBAAiB,KAAK,aAAa;UAC5C,aAAa,CAAC,iBAAiB,KAAK,oBAAoB;;;;;;;;mBAQ/C,aAAa,IAAI,eAAe,IAAI,aAAa;cACtD,eAAe;sBACP,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;mBA0BzB,WAAW,IAAI,eAAe,IAAI,eAAe;cACtD,eAAe;;;;uBAIN,eAAe,QAAQ,eAAe;;CAE5D,CAAC","sourcesContent":["import { css } from '@ni/fast-element';\nimport {\n applicationBackgroundColor,\n borderWidth,\n dividerBackgroundColor,\n errorTextFont,\n sectionBackgroundImage,\n mediumPadding,\n standardPadding,\n placeholderFontColor,\n linkFont,\n linkFontColor,\n} from '@ni/nimble-components/dist/esm/theme-provider/design-tokens';\nimport { display } from '../../utilities/style/display';\n\nexport const styles = css`\n ${display('flex')}\n\n :host {\n height: 480px;\n flex-direction: column;\n background: ${applicationBackgroundColor};\n }\n\n :host([appearance='overlay']) {\n background: none;\n }\n\n .toolbar {\n display: block;\n }\n\n .toolbar.toolbar-empty {\n display: none;\n }\n\n .start {\n display: block;\n }\n\n .start.start-empty {\n display: none;\n }\n\n .end {\n display: flex;\n flex-wrap: wrap;\n justify-content: center;\n padding: 2px ${standardPadding};\n border-top: ${borderWidth} solid ${dividerBackgroundColor};\n }\n\n .end.end-empty {\n display: none;\n }\n\n ::slotted([slot=\"end\"]) {\n font: ${errorTextFont};\n color: ${placeholderFontColor};\n ${linkFont.cssCustomProperty}: ${errorTextFont};\n ${linkFontColor.cssCustomProperty}: ${placeholderFontColor};\n }\n\n .messages {\n flex: 1;\n display: flex;\n flex-direction: column;\n row-gap: 32px;\n padding: ${mediumPadding} ${standardPadding} ${mediumPadding}\n ${standardPadding};\n background: ${sectionBackgroundImage};\n overflow-y: auto;\n overflow-anchor: none;\n }\n\n .messages-history,\n .messages-anchored {\n flex: none;\n display: flex;\n flex-direction: column;\n row-gap: 32px;\n }\n\n .messages-history.region-empty {\n display: none;\n }\n\n .messages-anchored.anchor-active {\n min-height: 100%;\n }\n\n :host([appearance='overlay']) .messages {\n background: none;\n }\n\n .input {\n padding: ${borderWidth} ${standardPadding} ${standardPadding}\n ${standardPadding};\n }\n\n .input.input-empty {\n padding: 0px ${standardPadding} 0px ${standardPadding};\n }\n`;\n"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { html, slotted } from '@ni/fast-element';
|
|
1
|
+
import { html, ref, slotted } from '@ni/fast-element';
|
|
2
2
|
export const template = html `
|
|
3
3
|
<div class="toolbar ${x => (x.toolbarEmpty ? 'toolbar-empty' : '')}">
|
|
4
4
|
<slot name="toolbar" ${slotted({ property: 'slottedToolbarElements' })}></slot>
|
|
@@ -6,7 +6,14 @@ export const template = html `
|
|
|
6
6
|
<div class="start ${x => (x.startEmpty ? 'start-empty' : '')}">
|
|
7
7
|
<slot name="start" ${slotted({ property: 'slottedStartElements' })}></slot>
|
|
8
8
|
</div>
|
|
9
|
-
<div class="messages"
|
|
9
|
+
<div class="messages" ${ref('messagesContainer')}>
|
|
10
|
+
<div class="messages-history ${x => (x.historyEmpty ? 'region-empty' : '')}">
|
|
11
|
+
<slot name="history" ${slotted({ property: 'slottedHistoryMessages' })}></slot>
|
|
12
|
+
</div>
|
|
13
|
+
<div class="messages-anchored ${x => (x.autoScrollManager.anchorActive ? 'anchor-active' : '')}" ${ref('anchoredContainer')}>
|
|
14
|
+
<slot ${slotted({ property: 'slottedMessages' })}></slot>
|
|
15
|
+
</div>
|
|
16
|
+
</div>
|
|
10
17
|
<div class="input ${x => (x.inputEmpty ? 'input-empty' : '')}">
|
|
11
18
|
<slot name="input" ${slotted({ property: 'slottedInputElements' })}>
|
|
12
19
|
</slot>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"template.js","sourceRoot":"","sources":["../../../../src/chat/conversation/template.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"template.js","sourceRoot":"","sources":["../../../../src/chat/conversation/template.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAGtD,MAAM,CAAC,MAAM,QAAQ,GAAG,IAAI,CAAkB;sBACxB,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,EAAE,CAAC;2BACvC,OAAO,CAAC,EAAE,QAAQ,EAAE,wBAAwB,EAAE,CAAC;;oBAEtD,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,CAAC;yBACnC,OAAO,CAAC,EAAE,QAAQ,EAAE,sBAAsB,EAAE,CAAC;;wBAE9C,GAAG,CAAC,mBAAmB,CAAC;mCACb,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,EAAE,CAAC;+BAC/C,OAAO,CAAC,EAAE,QAAQ,EAAE,wBAAwB,EAAE,CAAC;;oCAE1C,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,iBAAiB,CAAC,YAAY,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,GAAG,CAAC,mBAAmB,CAAC;gBAC/G,OAAO,CAAC,EAAE,QAAQ,EAAE,iBAAiB,EAAE,CAAC;;;oBAGpC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,CAAC;yBACnC,OAAO,CAAC,EAAE,QAAQ,EAAE,sBAAsB,EAAE,CAAC;;;kBAGpD,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,CAAC;uBAC/B,OAAO,CAAC,EAAE,QAAQ,EAAE,oBAAoB,EAAE,CAAC;;CAEjE,CAAC","sourcesContent":["import { html, ref, slotted } from '@ni/fast-element';\nimport type { ChatConversation } from '.';\n\nexport const template = html<ChatConversation>`\n<div class=\"toolbar ${x => (x.toolbarEmpty ? 'toolbar-empty' : '')}\">\n <slot name=\"toolbar\" ${slotted({ property: 'slottedToolbarElements' })}></slot>\n</div>\n<div class=\"start ${x => (x.startEmpty ? 'start-empty' : '')}\">\n <slot name=\"start\" ${slotted({ property: 'slottedStartElements' })}></slot>\n</div>\n<div class=\"messages\" ${ref('messagesContainer')}>\n <div class=\"messages-history ${x => (x.historyEmpty ? 'region-empty' : '')}\">\n <slot name=\"history\" ${slotted({ property: 'slottedHistoryMessages' })}></slot>\n </div>\n <div class=\"messages-anchored ${x => (x.autoScrollManager.anchorActive ? 'anchor-active' : '')}\" ${ref('anchoredContainer')}>\n <slot ${slotted({ property: 'slottedMessages' })}></slot>\n </div>\n</div>\n<div class=\"input ${x => (x.inputEmpty ? 'input-empty' : '')}\">\n <slot name=\"input\" ${slotted({ property: 'slottedInputElements' })}>\n </slot>\n</div>\n<div class=\"end ${x => (x.endEmpty ? 'end-empty' : '')}\">\n <slot name=\"end\" ${slotted({ property: 'slottedEndElements' })}></slot>\n</div>\n`;\n"]}
|