@ni/spright-components 6.20.3 → 6.21.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 (35) hide show
  1. package/dist/all-components-bundle.js +1602 -474
  2. package/dist/all-components-bundle.js.map +1 -1
  3. package/dist/all-components-bundle.min.js +5374 -5086
  4. package/dist/all-components-bundle.min.js.map +1 -1
  5. package/dist/custom-elements.json +51 -0
  6. package/dist/custom-elements.md +10 -6
  7. package/dist/esm/chat/conversation/index.d.ts +22 -0
  8. package/dist/esm/chat/conversation/index.js +47 -0
  9. package/dist/esm/chat/conversation/index.js.map +1 -1
  10. package/dist/esm/chat/conversation/models/auto-scroll-manager.d.ts +53 -0
  11. package/dist/esm/chat/conversation/models/auto-scroll-manager.js +222 -0
  12. package/dist/esm/chat/conversation/models/auto-scroll-manager.js.map +1 -0
  13. package/dist/esm/chat/conversation/styles.js +18 -1
  14. package/dist/esm/chat/conversation/styles.js.map +1 -1
  15. package/dist/esm/chat/conversation/template.js +9 -2
  16. package/dist/esm/chat/conversation/template.js.map +1 -1
  17. package/dist/esm/chat/conversation/testing/chat-conversation.pageobject.d.ts +55 -0
  18. package/dist/esm/chat/conversation/testing/chat-conversation.pageobject.js +194 -0
  19. package/dist/esm/chat/conversation/testing/chat-conversation.pageobject.js.map +1 -0
  20. package/dist/esm/chat/message/inbound/index.d.ts +3 -0
  21. package/dist/esm/chat/message/inbound/index.js +3 -0
  22. package/dist/esm/chat/message/inbound/index.js.map +1 -1
  23. package/dist/esm/chat/message/models/chat-message-internals.d.ts +38 -0
  24. package/dist/esm/chat/message/models/chat-message-internals.js +39 -0
  25. package/dist/esm/chat/message/models/chat-message-internals.js.map +1 -0
  26. package/dist/esm/chat/message/outbound/index.d.ts +3 -0
  27. package/dist/esm/chat/message/outbound/index.js +8 -0
  28. package/dist/esm/chat/message/outbound/index.js.map +1 -1
  29. package/dist/esm/chat/message/system/index.d.ts +3 -0
  30. package/dist/esm/chat/message/system/index.js +6 -0
  31. package/dist/esm/chat/message/system/index.js.map +1 -1
  32. package/dist/esm/chat/message/testing/chat-message.pageobject.d.ts +18 -0
  33. package/dist/esm/chat/message/testing/chat-message.pageobject.js +27 -0
  34. package/dist/esm/chat/message/testing/chat-message.pageobject.js.map +1 -0
  35. package/package.json +2 -2
@@ -0,0 +1,55 @@
1
+ import type { ChatConversation } from '..';
2
+ /**
3
+ * Page object for the `spright-chat-conversation`
4
+ */
5
+ export declare class ChatConversationPageObject {
6
+ protected readonly element: ChatConversation;
7
+ constructor(element: ChatConversation);
8
+ hasDefaultSlot(): boolean;
9
+ hasNamedSlot(name: string): boolean;
10
+ getNamedSlotAssignedElementCount(name: string): number;
11
+ getMessageCount(): number;
12
+ getMessageTextByIndex(index: number): string;
13
+ getLastMessageText(): string;
14
+ isMessageOutboundByIndex(index: number): boolean;
15
+ getScrollTop(): number;
16
+ getScrollHeight(): number;
17
+ getClientHeight(): number;
18
+ isAnchorRegionReserved(): boolean;
19
+ getHistoryRegionMessageCount(): number;
20
+ getAnchoredRegionMessageCount(): number;
21
+ getMessageSlotByIndex(index: number): string;
22
+ getAnchoredRegionHeight(): number;
23
+ getMessageViewportTop(index: number): number;
24
+ isMessagePinnedNearTop(index: number, tolerance?: number): boolean;
25
+ getDistanceFromBottom(): number;
26
+ isScrolledToBottom(threshold?: number): boolean;
27
+ isAutoScrollEnabled(): boolean;
28
+ isAutoScrollConnected(): boolean;
29
+ isAutoScrollEngaged(): boolean;
30
+ isMessageScrollAnchorByIndex(index: number): boolean;
31
+ getProgrammaticScrollTarget(): number | undefined;
32
+ setProgrammaticScrollTarget(target: number): void;
33
+ dispatchScroll(): void;
34
+ private get autoScrollManagerInternals();
35
+ appendOutboundMessage(text: string): Promise<void>;
36
+ appendInboundMessage(text: string): Promise<void>;
37
+ appendSystemMessage(text: string): Promise<void>;
38
+ appendTextToLastMessage(text: string): Promise<void>;
39
+ setViewportHeight(height: number): Promise<void>;
40
+ setAutoScrollEnabled(enabled: boolean): Promise<void>;
41
+ scrollToOffset(offset: number): Promise<void>;
42
+ scrollByOffset(delta: number): Promise<void>;
43
+ scrollToBottom(): Promise<void>;
44
+ /**
45
+ * Waits for FAST updates plus two animation frames so reactions driven by
46
+ * `requestAnimationFrame` and `ResizeObserver` (used by scroll management)
47
+ * have settled.
48
+ */
49
+ waitForScrollUpdateAsync(): Promise<void>;
50
+ private nextAnimationFrameAsync;
51
+ private appendMessage;
52
+ private getMessages;
53
+ private getScrollContainer;
54
+ private queryShadow;
55
+ }
@@ -0,0 +1,194 @@
1
+ import { waitForUpdatesAsync } from '@ni/nimble-components/dist/esm/testing/async-helpers';
2
+ import { chatMessageInboundTag } from '../../message/inbound';
3
+ import { chatMessageOutboundTag } from '../../message/outbound';
4
+ import { chatMessageSystemTag } from '../../message/system';
5
+ /**
6
+ * Page object for the `spright-chat-conversation`
7
+ */
8
+ export class ChatConversationPageObject {
9
+ constructor(element) {
10
+ this.element = element;
11
+ }
12
+ // --- slots ---
13
+ hasDefaultSlot() {
14
+ return this.queryShadow('.messages slot:not([name])') !== undefined;
15
+ }
16
+ hasNamedSlot(name) {
17
+ return this.queryShadow(`slot[name="${name}"]`) !== undefined;
18
+ }
19
+ getNamedSlotAssignedElementCount(name) {
20
+ const slot = this.queryShadow(`slot[name="${name}"]`);
21
+ return slot?.assignedElements().length ?? 0;
22
+ }
23
+ // --- messages ---
24
+ getMessageCount() {
25
+ return this.getMessages().length;
26
+ }
27
+ getMessageTextByIndex(index) {
28
+ const message = this.getMessages()[index];
29
+ if (message === undefined) {
30
+ throw new Error(`No message at index ${index}`);
31
+ }
32
+ return message.textContent?.trim() ?? '';
33
+ }
34
+ getLastMessageText() {
35
+ return this.getMessageTextByIndex(this.getMessageCount() - 1);
36
+ }
37
+ isMessageOutboundByIndex(index) {
38
+ const message = this.getMessages()[index];
39
+ return message?.tagName.toLowerCase() === chatMessageOutboundTag;
40
+ }
41
+ // --- scroll metrics ---
42
+ getScrollTop() {
43
+ return this.getScrollContainer().scrollTop;
44
+ }
45
+ getScrollHeight() {
46
+ return this.getScrollContainer().scrollHeight;
47
+ }
48
+ getClientHeight() {
49
+ return this.getScrollContainer().clientHeight;
50
+ }
51
+ isAnchorRegionReserved() {
52
+ return this.element.autoScrollManager.anchorActive;
53
+ }
54
+ getHistoryRegionMessageCount() {
55
+ return this.getNamedSlotAssignedElementCount('history');
56
+ }
57
+ getAnchoredRegionMessageCount() {
58
+ const slot = this.queryShadow('.messages-anchored slot');
59
+ return slot?.assignedElements().length ?? 0;
60
+ }
61
+ getMessageSlotByIndex(index) {
62
+ const message = this.getMessages()[index];
63
+ return message?.getAttribute('slot') ?? '';
64
+ }
65
+ getAnchoredRegionHeight() {
66
+ const region = this.queryShadow('.messages-anchored');
67
+ return region?.clientHeight ?? 0;
68
+ }
69
+ getMessageViewportTop(index) {
70
+ const message = this.getMessages()[index];
71
+ if (message === undefined) {
72
+ throw new Error(`No message at index ${index}`);
73
+ }
74
+ const container = this.getScrollContainer();
75
+ return (message.getBoundingClientRect().top
76
+ - container.getBoundingClientRect().top);
77
+ }
78
+ isMessagePinnedNearTop(index, tolerance = 48) {
79
+ return Math.abs(this.getMessageViewportTop(index)) <= tolerance;
80
+ }
81
+ getDistanceFromBottom() {
82
+ const container = this.getScrollContainer();
83
+ return (container.scrollHeight - container.scrollTop - container.clientHeight);
84
+ }
85
+ isScrolledToBottom(threshold = 10) {
86
+ return this.getDistanceFromBottom() <= threshold;
87
+ }
88
+ // --- auto-scroll state ---
89
+ isAutoScrollEnabled() {
90
+ return this.element.autoScroll;
91
+ }
92
+ isAutoScrollConnected() {
93
+ return this.element.autoScrollManager.isActive;
94
+ }
95
+ isAutoScrollEngaged() {
96
+ return this.element.autoScrollManager.autoScrollEngaged;
97
+ }
98
+ isMessageScrollAnchorByIndex(index) {
99
+ const message = this.getMessages()[index];
100
+ return message?.messageInternals?.isScrollAnchor ?? false;
101
+ }
102
+ // --- programmatic scroll handling (test seams) ---
103
+ getProgrammaticScrollTarget() {
104
+ return this.autoScrollManagerInternals.programmaticScrollTarget;
105
+ }
106
+ setProgrammaticScrollTarget(target) {
107
+ this.autoScrollManagerInternals.programmaticScrollTarget = target;
108
+ }
109
+ dispatchScroll() {
110
+ this.getScrollContainer().dispatchEvent(new Event('scroll'));
111
+ }
112
+ get autoScrollManagerInternals() {
113
+ return this.element.autoScrollManager;
114
+ }
115
+ // --- actions ---
116
+ async appendOutboundMessage(text) {
117
+ await this.appendMessage(chatMessageOutboundTag, text);
118
+ }
119
+ async appendInboundMessage(text) {
120
+ await this.appendMessage(chatMessageInboundTag, text);
121
+ }
122
+ async appendSystemMessage(text) {
123
+ await this.appendMessage(chatMessageSystemTag, text);
124
+ }
125
+ async appendTextToLastMessage(text) {
126
+ const messages = this.getMessages();
127
+ const lastMessage = messages[messages.length - 1];
128
+ if (lastMessage === undefined) {
129
+ throw new Error('No message to append text to');
130
+ }
131
+ lastMessage.textContent = `${lastMessage.textContent ?? ''}${text}`;
132
+ await this.waitForScrollUpdateAsync();
133
+ }
134
+ async setViewportHeight(height) {
135
+ this.element.style.height = `${height}px`;
136
+ await this.waitForScrollUpdateAsync();
137
+ }
138
+ async setAutoScrollEnabled(enabled) {
139
+ this.element.autoScroll = enabled;
140
+ await this.waitForScrollUpdateAsync();
141
+ }
142
+ async scrollToOffset(offset) {
143
+ this.getScrollContainer().scrollTop = offset;
144
+ await this.waitForScrollUpdateAsync();
145
+ }
146
+ async scrollByOffset(delta) {
147
+ this.getScrollContainer().scrollTop += delta;
148
+ await this.waitForScrollUpdateAsync();
149
+ }
150
+ async scrollToBottom() {
151
+ await this.scrollToOffset(this.getScrollHeight());
152
+ }
153
+ /**
154
+ * Waits for FAST updates plus two animation frames so reactions driven by
155
+ * `requestAnimationFrame` and `ResizeObserver` (used by scroll management)
156
+ * have settled.
157
+ */
158
+ async waitForScrollUpdateAsync() {
159
+ await waitForUpdatesAsync();
160
+ await this.nextAnimationFrameAsync();
161
+ await this.nextAnimationFrameAsync();
162
+ await waitForUpdatesAsync();
163
+ }
164
+ async nextAnimationFrameAsync() {
165
+ await new Promise(resolve => {
166
+ requestAnimationFrame(() => resolve());
167
+ });
168
+ }
169
+ async appendMessage(tag, text) {
170
+ const message = document.createElement(tag);
171
+ message.textContent = text;
172
+ this.element.appendChild(message);
173
+ await this.waitForScrollUpdateAsync();
174
+ }
175
+ getMessages() {
176
+ const history = this.queryShadow('slot[name="history"]');
177
+ const anchored = this.queryShadow('.messages-anchored slot');
178
+ return [
179
+ ...(history?.assignedElements({ flatten: true }) ?? []),
180
+ ...(anchored?.assignedElements({ flatten: true }) ?? [])
181
+ ];
182
+ }
183
+ getScrollContainer() {
184
+ const container = this.queryShadow('.messages');
185
+ if (container === undefined) {
186
+ throw new Error('Scroll container not found');
187
+ }
188
+ return container;
189
+ }
190
+ queryShadow(selector) {
191
+ return this.element.shadowRoot?.querySelector(selector) ?? undefined;
192
+ }
193
+ }
194
+ //# sourceMappingURL=chat-conversation.pageobject.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chat-conversation.pageobject.js","sourceRoot":"","sources":["../../../../../src/chat/conversation/testing/chat-conversation.pageobject.ts"],"names":[],"mappings":"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","sourcesContent":["import { waitForUpdatesAsync } from '@ni/nimble-components/dist/esm/testing/async-helpers';\nimport type { ChatConversation } from '..';\nimport { chatMessageInboundTag } from '../../message/inbound';\nimport { chatMessageOutboundTag } from '../../message/outbound';\nimport { chatMessageSystemTag } from '../../message/system';\n\n/**\n * Page object for the `spright-chat-conversation`\n */\nexport class ChatConversationPageObject {\n public constructor(protected readonly element: ChatConversation) {}\n\n // --- slots ---\n\n public hasDefaultSlot(): boolean {\n return this.queryShadow('.messages slot:not([name])') !== undefined;\n }\n\n public hasNamedSlot(name: string): boolean {\n return this.queryShadow(`slot[name=\"${name}\"]`) !== undefined;\n }\n\n public getNamedSlotAssignedElementCount(name: string): number {\n const slot = this.queryShadow(`slot[name=\"${name}\"]`) as\n | HTMLSlotElement\n | undefined;\n return slot?.assignedElements().length ?? 0;\n }\n\n // --- messages ---\n\n public getMessageCount(): number {\n return this.getMessages().length;\n }\n\n public getMessageTextByIndex(index: number): string {\n const message = this.getMessages()[index];\n if (message === undefined) {\n throw new Error(`No message at index ${index}`);\n }\n return message.textContent?.trim() ?? '';\n }\n\n public getLastMessageText(): string {\n return this.getMessageTextByIndex(this.getMessageCount() - 1);\n }\n\n public isMessageOutboundByIndex(index: number): boolean {\n const message = this.getMessages()[index];\n return message?.tagName.toLowerCase() === chatMessageOutboundTag;\n }\n\n // --- scroll metrics ---\n\n public getScrollTop(): number {\n return this.getScrollContainer().scrollTop;\n }\n\n public getScrollHeight(): number {\n return this.getScrollContainer().scrollHeight;\n }\n\n public getClientHeight(): number {\n return this.getScrollContainer().clientHeight;\n }\n\n public isAnchorRegionReserved(): boolean {\n return this.element.autoScrollManager.anchorActive;\n }\n\n public getHistoryRegionMessageCount(): number {\n return this.getNamedSlotAssignedElementCount('history');\n }\n\n public getAnchoredRegionMessageCount(): number {\n const slot = this.queryShadow('.messages-anchored slot') as\n | HTMLSlotElement\n | undefined;\n return slot?.assignedElements().length ?? 0;\n }\n\n public getMessageSlotByIndex(index: number): string {\n const message = this.getMessages()[index];\n return message?.getAttribute('slot') ?? '';\n }\n\n public getAnchoredRegionHeight(): number {\n const region = this.queryShadow('.messages-anchored') as\n | HTMLElement\n | undefined;\n return region?.clientHeight ?? 0;\n }\n\n public getMessageViewportTop(index: number): number {\n const message = this.getMessages()[index];\n if (message === undefined) {\n throw new Error(`No message at index ${index}`);\n }\n const container = this.getScrollContainer();\n return (\n message.getBoundingClientRect().top\n - container.getBoundingClientRect().top\n );\n }\n\n public isMessagePinnedNearTop(index: number, tolerance = 48): boolean {\n return Math.abs(this.getMessageViewportTop(index)) <= tolerance;\n }\n\n public getDistanceFromBottom(): number {\n const container = this.getScrollContainer();\n return (\n container.scrollHeight - container.scrollTop - container.clientHeight\n );\n }\n\n public isScrolledToBottom(threshold = 10): boolean {\n return this.getDistanceFromBottom() <= threshold;\n }\n\n // --- auto-scroll state ---\n\n public isAutoScrollEnabled(): boolean {\n return this.element.autoScroll;\n }\n\n public isAutoScrollConnected(): boolean {\n return this.element.autoScrollManager.isActive;\n }\n\n public isAutoScrollEngaged(): boolean {\n return this.element.autoScrollManager.autoScrollEngaged;\n }\n\n public isMessageScrollAnchorByIndex(index: number): boolean {\n const message = this.getMessages()[index] as unknown as\n | { messageInternals?: { isScrollAnchor: boolean } }\n | undefined;\n return message?.messageInternals?.isScrollAnchor ?? false;\n }\n\n // --- programmatic scroll handling (test seams) ---\n\n public getProgrammaticScrollTarget(): number | undefined {\n return this.autoScrollManagerInternals.programmaticScrollTarget;\n }\n\n public setProgrammaticScrollTarget(target: number): void {\n this.autoScrollManagerInternals.programmaticScrollTarget = target;\n }\n\n public dispatchScroll(): void {\n this.getScrollContainer().dispatchEvent(new Event('scroll'));\n }\n\n private get autoScrollManagerInternals(): {\n programmaticScrollTarget?: number\n } {\n return this.element.autoScrollManager as unknown as {\n programmaticScrollTarget?: number\n };\n }\n\n // --- actions ---\n\n public async appendOutboundMessage(text: string): Promise<void> {\n await this.appendMessage(chatMessageOutboundTag, text);\n }\n\n public async appendInboundMessage(text: string): Promise<void> {\n await this.appendMessage(chatMessageInboundTag, text);\n }\n\n public async appendSystemMessage(text: string): Promise<void> {\n await this.appendMessage(chatMessageSystemTag, text);\n }\n\n public async appendTextToLastMessage(text: string): Promise<void> {\n const messages = this.getMessages();\n const lastMessage = messages[messages.length - 1];\n if (lastMessage === undefined) {\n throw new Error('No message to append text to');\n }\n lastMessage.textContent = `${lastMessage.textContent ?? ''}${text}`;\n await this.waitForScrollUpdateAsync();\n }\n\n public async setViewportHeight(height: number): Promise<void> {\n this.element.style.height = `${height}px`;\n await this.waitForScrollUpdateAsync();\n }\n\n public async setAutoScrollEnabled(enabled: boolean): Promise<void> {\n this.element.autoScroll = enabled;\n await this.waitForScrollUpdateAsync();\n }\n\n public async scrollToOffset(offset: number): Promise<void> {\n this.getScrollContainer().scrollTop = offset;\n await this.waitForScrollUpdateAsync();\n }\n\n public async scrollByOffset(delta: number): Promise<void> {\n this.getScrollContainer().scrollTop += delta;\n await this.waitForScrollUpdateAsync();\n }\n\n public async scrollToBottom(): Promise<void> {\n await this.scrollToOffset(this.getScrollHeight());\n }\n\n /**\n * Waits for FAST updates plus two animation frames so reactions driven by\n * `requestAnimationFrame` and `ResizeObserver` (used by scroll management)\n * have settled.\n */\n public async waitForScrollUpdateAsync(): Promise<void> {\n await waitForUpdatesAsync();\n await this.nextAnimationFrameAsync();\n await this.nextAnimationFrameAsync();\n await waitForUpdatesAsync();\n }\n\n private async nextAnimationFrameAsync(): Promise<void> {\n await new Promise<void>(resolve => {\n requestAnimationFrame(() => resolve());\n });\n }\n\n private async appendMessage(tag: string, text: string): Promise<void> {\n const message = document.createElement(tag);\n message.textContent = text;\n this.element.appendChild(message);\n await this.waitForScrollUpdateAsync();\n }\n\n private getMessages(): Element[] {\n const history = this.queryShadow('slot[name=\"history\"]') as\n | HTMLSlotElement\n | undefined;\n const anchored = this.queryShadow('.messages-anchored slot') as\n | HTMLSlotElement\n | undefined;\n return [\n ...(history?.assignedElements({ flatten: true }) ?? []),\n ...(anchored?.assignedElements({ flatten: true }) ?? [])\n ];\n }\n\n private getScrollContainer(): HTMLElement {\n const container = this.queryShadow('.messages') as\n | HTMLElement\n | undefined;\n if (container === undefined) {\n throw new Error('Scroll container not found');\n }\n return container;\n }\n\n private queryShadow(selector: string): Element | undefined {\n return this.element.shadowRoot?.querySelector(selector) ?? undefined;\n }\n}\n"]}
@@ -1,4 +1,5 @@
1
1
  import { FoundationElement, type FoundationElementDefinition, type StartEndOptions } from '@ni/fast-foundation';
2
+ import { ChatMessageInternals } from '../models/chat-message-internals';
2
3
  declare global {
3
4
  interface HTMLElementTagNameMap {
4
5
  'spright-chat-message-inbound': ChatMessageInbound;
@@ -13,6 +14,8 @@ export type ChatMessageInboundOptions = FoundationElementDefinition & StartEndOp
13
14
  * A Spright component for displaying a chat message
14
15
  */
15
16
  export declare class ChatMessageInbound extends FoundationElement {
17
+ /** @internal */
18
+ readonly messageInternals: ChatMessageInternals;
16
19
  /** @internal */
17
20
  footerActionsIsEmpty: boolean;
18
21
  /** @internal */
@@ -3,6 +3,7 @@ import { observable } from '@ni/fast-element';
3
3
  import { applyMixins, DesignSystem, FoundationElement, StartEnd } from '@ni/fast-foundation';
4
4
  import { styles } from './styles';
5
5
  import { template } from './template';
6
+ import { ChatMessageInternals } from '../models/chat-message-internals';
6
7
  /**
7
8
  * A Spright component for displaying a chat message
8
9
  */
@@ -10,6 +11,8 @@ export class ChatMessageInbound extends FoundationElement {
10
11
  constructor() {
11
12
  super(...arguments);
12
13
  /** @internal */
14
+ this.messageInternals = new ChatMessageInternals(this);
15
+ /** @internal */
13
16
  this.footerActionsIsEmpty = true;
14
17
  }
15
18
  slottedFooterActionsElementsChanged(_prev, next) {
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../src/chat/message/inbound/index.ts"],"names":[],"mappings":";AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EACH,WAAW,EACX,YAAY,EACZ,iBAAiB,EACjB,QAAQ,EAGX,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AActC;;GAEG;AACH,MAAM,OAAO,kBAAmB,SAAQ,iBAAiB;IAAzD;;QACI,gBAAgB;QAET,yBAAoB,GAAG,IAAI,CAAC;IAYvC,CAAC;IANU,mCAAmC,CACtC,KAAgC,EAChC,IAA+B;QAE/B,IAAI,CAAC,oBAAoB,GAAG,IAAI,KAAK,SAAS,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,CAAC;IACxE,CAAC;CACJ;AAZU;IADN,UAAU;gEACwB;AAInB;IADf,UAAU;wEACkD;AASjE,WAAW,CAAC,kBAAkB,EAAE,QAAQ,CAAC,CAAC;AAE1C,MAAM,yBAAyB,GAAG,kBAAkB,CAAC,OAAO,CAAC;IACzD,QAAQ,EAAE,sBAAsB;IAChC,QAAQ;IACR,MAAM;CACT,CAAC,CAAC;AAEH,YAAY,CAAC,WAAW,EAAE,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,QAAQ,CAAC,yBAAyB,EAAE,CAAC,CAAC;AACvF,MAAM,CAAC,MAAM,qBAAqB,GAAG,8BAA8B,CAAC","sourcesContent":["import { observable } from '@ni/fast-element';\nimport {\n applyMixins,\n DesignSystem,\n FoundationElement,\n StartEnd,\n type FoundationElementDefinition,\n type StartEndOptions\n} from '@ni/fast-foundation';\nimport { styles } from './styles';\nimport { template } from './template';\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'spright-chat-message-inbound': ChatMessageInbound;\n }\n}\n\n/**\n * SprightChatMessageInbound configuration options\n * @public\n */\nexport type ChatMessageInboundOptions = FoundationElementDefinition & StartEndOptions;\n\n/**\n * A Spright component for displaying a chat message\n */\nexport class ChatMessageInbound extends FoundationElement {\n /** @internal */\n @observable\n public footerActionsIsEmpty = true;\n\n /** @internal */\n @observable\n public readonly slottedFooterActionsElements?: HTMLElement[];\n\n public slottedFooterActionsElementsChanged(\n _prev: HTMLElement[] | undefined,\n next: HTMLElement[] | undefined\n ): void {\n this.footerActionsIsEmpty = next === undefined || next.length === 0;\n }\n}\napplyMixins(ChatMessageInbound, StartEnd);\n\nconst sprightChatMessageInbound = ChatMessageInbound.compose({\n baseName: 'chat-message-inbound',\n template,\n styles\n});\n\nDesignSystem.getOrCreate().withPrefix('spright').register(sprightChatMessageInbound());\nexport const chatMessageInboundTag = 'spright-chat-message-inbound';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../src/chat/message/inbound/index.ts"],"names":[],"mappings":";AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EACH,WAAW,EACX,YAAY,EACZ,iBAAiB,EACjB,QAAQ,EAGX,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,oBAAoB,EAAE,MAAM,kCAAkC,CAAC;AAcxE;;GAEG;AACH,MAAM,OAAO,kBAAmB,SAAQ,iBAAiB;IAAzD;;QACI,gBAAgB;QACA,qBAAgB,GAAG,IAAI,oBAAoB,CAAC,IAAI,CAAC,CAAC;QAElE,gBAAgB;QAET,yBAAoB,GAAG,IAAI,CAAC;IAYvC,CAAC;IANU,mCAAmC,CACtC,KAAgC,EAChC,IAA+B;QAE/B,IAAI,CAAC,oBAAoB,GAAG,IAAI,KAAK,SAAS,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,CAAC;IACxE,CAAC;CACJ;AAZU;IADN,UAAU;gEACwB;AAInB;IADf,UAAU;wEACkD;AASjE,WAAW,CAAC,kBAAkB,EAAE,QAAQ,CAAC,CAAC;AAE1C,MAAM,yBAAyB,GAAG,kBAAkB,CAAC,OAAO,CAAC;IACzD,QAAQ,EAAE,sBAAsB;IAChC,QAAQ;IACR,MAAM;CACT,CAAC,CAAC;AAEH,YAAY,CAAC,WAAW,EAAE,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,QAAQ,CAAC,yBAAyB,EAAE,CAAC,CAAC;AACvF,MAAM,CAAC,MAAM,qBAAqB,GAAG,8BAA8B,CAAC","sourcesContent":["import { observable } from '@ni/fast-element';\nimport {\n applyMixins,\n DesignSystem,\n FoundationElement,\n StartEnd,\n type FoundationElementDefinition,\n type StartEndOptions\n} from '@ni/fast-foundation';\nimport { styles } from './styles';\nimport { template } from './template';\nimport { ChatMessageInternals } from '../models/chat-message-internals';\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'spright-chat-message-inbound': ChatMessageInbound;\n }\n}\n\n/**\n * SprightChatMessageInbound configuration options\n * @public\n */\nexport type ChatMessageInboundOptions = FoundationElementDefinition & StartEndOptions;\n\n/**\n * A Spright component for displaying a chat message\n */\nexport class ChatMessageInbound extends FoundationElement {\n /** @internal */\n public readonly messageInternals = new ChatMessageInternals(this);\n\n /** @internal */\n @observable\n public footerActionsIsEmpty = true;\n\n /** @internal */\n @observable\n public readonly slottedFooterActionsElements?: HTMLElement[];\n\n public slottedFooterActionsElementsChanged(\n _prev: HTMLElement[] | undefined,\n next: HTMLElement[] | undefined\n ): void {\n this.footerActionsIsEmpty = next === undefined || next.length === 0;\n }\n}\napplyMixins(ChatMessageInbound, StartEnd);\n\nconst sprightChatMessageInbound = ChatMessageInbound.compose({\n baseName: 'chat-message-inbound',\n template,\n styles\n});\n\nDesignSystem.getOrCreate().withPrefix('spright').register(sprightChatMessageInbound());\nexport const chatMessageInboundTag = 'spright-chat-message-inbound';\n"]}
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Options for configuring a `ChatMessageInternals` instance.
3
+ * @internal
4
+ */
5
+ export interface ChatMessageInternalsOptions {
6
+ /** When true, the conversation anchors to this message when it is inserted. */
7
+ anchorOnInsert?: boolean;
8
+ }
9
+ /**
10
+ * Internal state for a chat message
11
+ * @internal
12
+ */
13
+ export declare class ChatMessageInternals {
14
+ /**
15
+ * Declares that the conversation should anchor to this message (pin it near
16
+ * the top of the viewport) when it is inserted.
17
+ */
18
+ readonly anchorOnInsert: boolean;
19
+ /**
20
+ * True when this message is the one the conversation anchors to while
21
+ * auto-scrolling.
22
+ */
23
+ isScrollAnchor: boolean;
24
+ private readonly host;
25
+ private slotName?;
26
+ constructor(host: HTMLElement, options?: ChatMessageInternalsOptions);
27
+ get slot(): string | undefined;
28
+ set slot(value: string | undefined);
29
+ static elementHasMessageInternals(element: Element): element is ChatMessage;
30
+ }
31
+ /**
32
+ * An element that exposes `ChatMessageInternals` for the conversation to read and
33
+ * coordinate scroll management.
34
+ * @internal
35
+ */
36
+ export interface ChatMessage extends HTMLElement {
37
+ readonly messageInternals: ChatMessageInternals;
38
+ }
@@ -0,0 +1,39 @@
1
+ import { __decorate } from "tslib";
2
+ import { observable } from '@ni/fast-element';
3
+ /**
4
+ * Internal state for a chat message
5
+ * @internal
6
+ */
7
+ export class ChatMessageInternals {
8
+ constructor(host, options) {
9
+ /**
10
+ * True when this message is the one the conversation anchors to while
11
+ * auto-scrolling.
12
+ */
13
+ this.isScrollAnchor = false;
14
+ this.host = host;
15
+ this.anchorOnInsert = options?.anchorOnInsert ?? false;
16
+ }
17
+ get slot() {
18
+ return this.slotName;
19
+ }
20
+ set slot(value) {
21
+ if (value === this.slotName) {
22
+ return;
23
+ }
24
+ this.slotName = value;
25
+ if (value === undefined) {
26
+ this.host.removeAttribute('slot');
27
+ }
28
+ else {
29
+ this.host.setAttribute('slot', value);
30
+ }
31
+ }
32
+ static elementHasMessageInternals(element) {
33
+ return element.messageInternals instanceof ChatMessageInternals;
34
+ }
35
+ }
36
+ __decorate([
37
+ observable
38
+ ], ChatMessageInternals.prototype, "isScrollAnchor", void 0);
39
+ //# sourceMappingURL=chat-message-internals.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chat-message-internals.js","sourceRoot":"","sources":["../../../../../src/chat/message/models/chat-message-internals.ts"],"names":[],"mappings":";AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAW9C;;;GAGG;AACH,MAAM,OAAO,oBAAoB;IAiB7B,YAAmB,IAAiB,EAAE,OAAqC;QAV3E;;;WAGG;QAEI,mBAAc,GAAG,KAAK,CAAC;QAM1B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,cAAc,GAAG,OAAO,EAAE,cAAc,IAAI,KAAK,CAAC;IAC3D,CAAC;IAED,IAAW,IAAI;QACX,OAAO,IAAI,CAAC,QAAQ,CAAC;IACzB,CAAC;IAED,IAAW,IAAI,CAAC,KAAyB;QACrC,IAAI,KAAK,KAAK,IAAI,CAAC,QAAQ,EAAE,CAAC;YAC1B,OAAO;QACX,CAAC;QACD,IAAI,CAAC,QAAQ,GAAG,KAAK,CAAC;QACtB,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACtB,IAAI,CAAC,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC;QACtC,CAAC;aAAM,CAAC;YACJ,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;QAC1C,CAAC;IACL,CAAC;IAEM,MAAM,CAAC,0BAA0B,CACpC,OAAgB;QAEhB,OAAQ,OAAgC,CAAC,gBAAgB,YAAY,oBAAoB,CAAC;IAC9F,CAAC;CACJ;AA/BU;IADN,UAAU;4DACmB","sourcesContent":["import { observable } from '@ni/fast-element';\n\n/**\n * Options for configuring a `ChatMessageInternals` instance.\n * @internal\n */\nexport interface ChatMessageInternalsOptions {\n /** When true, the conversation anchors to this message when it is inserted. */\n anchorOnInsert?: boolean;\n}\n\n/**\n * Internal state for a chat message\n * @internal\n */\nexport class ChatMessageInternals {\n /**\n * Declares that the conversation should anchor to this message (pin it near\n * the top of the viewport) when it is inserted.\n */\n public readonly anchorOnInsert: boolean;\n\n /**\n * True when this message is the one the conversation anchors to while\n * auto-scrolling.\n */\n @observable\n public isScrollAnchor = false;\n\n private readonly host: HTMLElement;\n private slotName?: string;\n\n public constructor(host: HTMLElement, options?: ChatMessageInternalsOptions) {\n this.host = host;\n this.anchorOnInsert = options?.anchorOnInsert ?? false;\n }\n\n public get slot(): string | undefined {\n return this.slotName;\n }\n\n public set slot(value: string | undefined) {\n if (value === this.slotName) {\n return;\n }\n this.slotName = value;\n if (value === undefined) {\n this.host.removeAttribute('slot');\n } else {\n this.host.setAttribute('slot', value);\n }\n }\n\n public static elementHasMessageInternals(\n element: Element\n ): element is ChatMessage {\n return (element as Partial<ChatMessage>).messageInternals instanceof ChatMessageInternals;\n }\n}\n\n/**\n * An element that exposes `ChatMessageInternals` for the conversation to read and\n * coordinate scroll management.\n * @internal\n */\nexport interface ChatMessage extends HTMLElement {\n readonly messageInternals: ChatMessageInternals;\n}\n"]}
@@ -1,4 +1,5 @@
1
1
  import { FoundationElement } from '@ni/fast-foundation';
2
+ import { ChatMessageInternals } from '../models/chat-message-internals';
2
3
  declare global {
3
4
  interface HTMLElementTagNameMap {
4
5
  'spright-chat-message-outbound': ChatMessageOutbound;
@@ -8,5 +9,7 @@ declare global {
8
9
  * A Spright component for displaying an outbound chat message
9
10
  */
10
11
  export declare class ChatMessageOutbound extends FoundationElement {
12
+ /** @internal */
13
+ readonly messageInternals: ChatMessageInternals;
11
14
  }
12
15
  export declare const chatMessageOutboundTag = "spright-chat-message-outbound";
@@ -1,10 +1,18 @@
1
1
  import { DesignSystem, FoundationElement, } from '@ni/fast-foundation';
2
2
  import { styles } from './styles';
3
3
  import { template } from './template';
4
+ import { ChatMessageInternals } from '../models/chat-message-internals';
4
5
  /**
5
6
  * A Spright component for displaying an outbound chat message
6
7
  */
7
8
  export class ChatMessageOutbound extends FoundationElement {
9
+ constructor() {
10
+ super(...arguments);
11
+ /** @internal */
12
+ this.messageInternals = new ChatMessageInternals(this, {
13
+ anchorOnInsert: true
14
+ });
15
+ }
8
16
  }
9
17
  const sprightChatMessageOutbound = ChatMessageOutbound.compose({
10
18
  baseName: 'chat-message-outbound',
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../src/chat/message/outbound/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,YAAY,EACZ,iBAAiB,GACpB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAQtC;;GAEG;AACH,MAAM,OAAO,mBAAoB,SAAQ,iBAAiB;CACzD;AAED,MAAM,0BAA0B,GAAG,mBAAmB,CAAC,OAAO,CAAC;IAC3D,QAAQ,EAAE,uBAAuB;IACjC,QAAQ;IACR,MAAM;CACT,CAAC,CAAC;AAEH,YAAY,CAAC,WAAW,EAAE,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,QAAQ,CAAC,0BAA0B,EAAE,CAAC,CAAC;AACxF,MAAM,CAAC,MAAM,sBAAsB,GAAG,+BAA+B,CAAC","sourcesContent":["import {\n DesignSystem,\n FoundationElement,\n} from '@ni/fast-foundation';\nimport { styles } from './styles';\nimport { template } from './template';\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'spright-chat-message-outbound': ChatMessageOutbound;\n }\n}\n\n/**\n * A Spright component for displaying an outbound chat message\n */\nexport class ChatMessageOutbound extends FoundationElement {\n}\n\nconst sprightChatMessageOutbound = ChatMessageOutbound.compose({\n baseName: 'chat-message-outbound',\n template,\n styles\n});\n\nDesignSystem.getOrCreate().withPrefix('spright').register(sprightChatMessageOutbound());\nexport const chatMessageOutboundTag = 'spright-chat-message-outbound';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../src/chat/message/outbound/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,YAAY,EACZ,iBAAiB,GACpB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,oBAAoB,EAAE,MAAM,kCAAkC,CAAC;AAQxE;;GAEG;AACH,MAAM,OAAO,mBAAoB,SAAQ,iBAAiB;IAA1D;;QACI,gBAAgB;QACA,qBAAgB,GAAG,IAAI,oBAAoB,CAAC,IAAI,EAAE;YAC9D,cAAc,EAAE,IAAI;SACvB,CAAC,CAAC;IACP,CAAC;CAAA;AAED,MAAM,0BAA0B,GAAG,mBAAmB,CAAC,OAAO,CAAC;IAC3D,QAAQ,EAAE,uBAAuB;IACjC,QAAQ;IACR,MAAM;CACT,CAAC,CAAC;AAEH,YAAY,CAAC,WAAW,EAAE,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,QAAQ,CAAC,0BAA0B,EAAE,CAAC,CAAC;AACxF,MAAM,CAAC,MAAM,sBAAsB,GAAG,+BAA+B,CAAC","sourcesContent":["import {\n DesignSystem,\n FoundationElement,\n} from '@ni/fast-foundation';\nimport { styles } from './styles';\nimport { template } from './template';\nimport { ChatMessageInternals } from '../models/chat-message-internals';\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'spright-chat-message-outbound': ChatMessageOutbound;\n }\n}\n\n/**\n * A Spright component for displaying an outbound chat message\n */\nexport class ChatMessageOutbound extends FoundationElement {\n /** @internal */\n public readonly messageInternals = new ChatMessageInternals(this, {\n anchorOnInsert: true\n });\n}\n\nconst sprightChatMessageOutbound = ChatMessageOutbound.compose({\n baseName: 'chat-message-outbound',\n template,\n styles\n});\n\nDesignSystem.getOrCreate().withPrefix('spright').register(sprightChatMessageOutbound());\nexport const chatMessageOutboundTag = 'spright-chat-message-outbound';\n"]}
@@ -1,4 +1,5 @@
1
1
  import { FoundationElement } from '@ni/fast-foundation';
2
+ import { ChatMessageInternals } from '../models/chat-message-internals';
2
3
  declare global {
3
4
  interface HTMLElementTagNameMap {
4
5
  'spright-chat-message-system': ChatMessageSystem;
@@ -8,5 +9,7 @@ declare global {
8
9
  * A Spright component for displaying an system chat message
9
10
  */
10
11
  export declare class ChatMessageSystem extends FoundationElement {
12
+ /** @internal */
13
+ readonly messageInternals: ChatMessageInternals;
11
14
  }
12
15
  export declare const chatMessageSystemTag = "spright-chat-message-system";
@@ -1,10 +1,16 @@
1
1
  import { DesignSystem, FoundationElement, } from '@ni/fast-foundation';
2
2
  import { styles } from './styles';
3
3
  import { template } from './template';
4
+ import { ChatMessageInternals } from '../models/chat-message-internals';
4
5
  /**
5
6
  * A Spright component for displaying an system chat message
6
7
  */
7
8
  export class ChatMessageSystem extends FoundationElement {
9
+ constructor() {
10
+ super(...arguments);
11
+ /** @internal */
12
+ this.messageInternals = new ChatMessageInternals(this);
13
+ }
8
14
  }
9
15
  const sprightChatMessageSystem = ChatMessageSystem.compose({
10
16
  baseName: 'chat-message-system',
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../src/chat/message/system/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,YAAY,EACZ,iBAAiB,GACpB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAQtC;;GAEG;AACH,MAAM,OAAO,iBAAkB,SAAQ,iBAAiB;CACvD;AAED,MAAM,wBAAwB,GAAG,iBAAiB,CAAC,OAAO,CAAC;IACvD,QAAQ,EAAE,qBAAqB;IAC/B,QAAQ;IACR,MAAM;CACT,CAAC,CAAC;AAEH,YAAY,CAAC,WAAW,EAAE,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,QAAQ,CAAC,wBAAwB,EAAE,CAAC,CAAC;AACtF,MAAM,CAAC,MAAM,oBAAoB,GAAG,6BAA6B,CAAC","sourcesContent":["import {\n DesignSystem,\n FoundationElement,\n} from '@ni/fast-foundation';\nimport { styles } from './styles';\nimport { template } from './template';\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'spright-chat-message-system': ChatMessageSystem;\n }\n}\n\n/**\n * A Spright component for displaying an system chat message\n */\nexport class ChatMessageSystem extends FoundationElement {\n}\n\nconst sprightChatMessageSystem = ChatMessageSystem.compose({\n baseName: 'chat-message-system',\n template,\n styles\n});\n\nDesignSystem.getOrCreate().withPrefix('spright').register(sprightChatMessageSystem());\nexport const chatMessageSystemTag = 'spright-chat-message-system';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../src/chat/message/system/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,YAAY,EACZ,iBAAiB,GACpB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,oBAAoB,EAAE,MAAM,kCAAkC,CAAC;AAQxE;;GAEG;AACH,MAAM,OAAO,iBAAkB,SAAQ,iBAAiB;IAAxD;;QACI,gBAAgB;QACA,qBAAgB,GAAG,IAAI,oBAAoB,CAAC,IAAI,CAAC,CAAC;IACtE,CAAC;CAAA;AAED,MAAM,wBAAwB,GAAG,iBAAiB,CAAC,OAAO,CAAC;IACvD,QAAQ,EAAE,qBAAqB;IAC/B,QAAQ;IACR,MAAM;CACT,CAAC,CAAC;AAEH,YAAY,CAAC,WAAW,EAAE,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,QAAQ,CAAC,wBAAwB,EAAE,CAAC,CAAC;AACtF,MAAM,CAAC,MAAM,oBAAoB,GAAG,6BAA6B,CAAC","sourcesContent":["import {\n DesignSystem,\n FoundationElement,\n} from '@ni/fast-foundation';\nimport { styles } from './styles';\nimport { template } from './template';\nimport { ChatMessageInternals } from '../models/chat-message-internals';\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'spright-chat-message-system': ChatMessageSystem;\n }\n}\n\n/**\n * A Spright component for displaying an system chat message\n */\nexport class ChatMessageSystem extends FoundationElement {\n /** @internal */\n public readonly messageInternals = new ChatMessageInternals(this);\n}\n\nconst sprightChatMessageSystem = ChatMessageSystem.compose({\n baseName: 'chat-message-system',\n template,\n styles\n});\n\nDesignSystem.getOrCreate().withPrefix('spright').register(sprightChatMessageSystem());\nexport const chatMessageSystemTag = 'spright-chat-message-system';\n"]}
@@ -0,0 +1,18 @@
1
+ import type { ChatMessageInbound } from '../inbound';
2
+ import type { ChatMessageOutbound } from '../outbound';
3
+ import type { ChatMessageSystem } from '../system';
4
+ type ChatMessageElement = ChatMessageInbound | ChatMessageOutbound | ChatMessageSystem;
5
+ /**
6
+ * Page object for the chat message components (`spright-chat-message-inbound`,
7
+ * `spright-chat-message-outbound`, and `spright-chat-message-system`) to provide
8
+ * consistent ways of querying the component during tests.
9
+ */
10
+ export declare class ChatMessagePageObject {
11
+ protected readonly element: ChatMessageElement;
12
+ constructor(element: ChatMessageElement);
13
+ getRenderedText(): string;
14
+ isContentRendered(): boolean;
15
+ isScrollAnchor(): boolean;
16
+ private getContentSlot;
17
+ }
18
+ export {};
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Page object for the chat message components (`spright-chat-message-inbound`,
3
+ * `spright-chat-message-outbound`, and `spright-chat-message-system`) to provide
4
+ * consistent ways of querying the component during tests.
5
+ */
6
+ export class ChatMessagePageObject {
7
+ constructor(element) {
8
+ this.element = element;
9
+ }
10
+ getRenderedText() {
11
+ const assigned = this.getContentSlot()?.assignedNodes({ flatten: true }) ?? [];
12
+ return assigned
13
+ .map(node => node.textContent ?? '')
14
+ .join('')
15
+ .trim();
16
+ }
17
+ isContentRendered() {
18
+ return this.getRenderedText().length > 0;
19
+ }
20
+ isScrollAnchor() {
21
+ return this.element.messageInternals.isScrollAnchor;
22
+ }
23
+ getContentSlot() {
24
+ return (this.element.shadowRoot?.querySelector('.message-content slot') ?? null);
25
+ }
26
+ }
27
+ //# sourceMappingURL=chat-message.pageobject.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chat-message.pageobject.js","sourceRoot":"","sources":["../../../../../src/chat/message/testing/chat-message.pageobject.ts"],"names":[],"mappings":"AAMA;;;;GAIG;AACH,MAAM,OAAO,qBAAqB;IAC9B,YAAsC,OAA2B;QAA3B,YAAO,GAAP,OAAO,CAAoB;IAAG,CAAC;IAE9D,eAAe;QAClB,MAAM,QAAQ,GAAG,IAAI,CAAC,cAAc,EAAE,EAAE,aAAa,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC;QAC/E,OAAO,QAAQ;aACV,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,WAAW,IAAI,EAAE,CAAC;aACnC,IAAI,CAAC,EAAE,CAAC;aACR,IAAI,EAAE,CAAC;IAChB,CAAC;IAEM,iBAAiB;QACpB,OAAO,IAAI,CAAC,eAAe,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC;IAC7C,CAAC;IAEM,cAAc;QACjB,OAAO,IAAI,CAAC,OAAO,CAAC,gBAAgB,CAAC,cAAc,CAAC;IACxD,CAAC;IAEO,cAAc;QAClB,OAAO,CACH,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,aAAa,CAClC,uBAAuB,CAC1B,IAAI,IAAI,CACZ,CAAC;IACN,CAAC;CACJ","sourcesContent":["import type { ChatMessageInbound } from '../inbound';\nimport type { ChatMessageOutbound } from '../outbound';\nimport type { ChatMessageSystem } from '../system';\n\ntype ChatMessageElement = ChatMessageInbound | ChatMessageOutbound | ChatMessageSystem;\n\n/**\n * Page object for the chat message components (`spright-chat-message-inbound`,\n * `spright-chat-message-outbound`, and `spright-chat-message-system`) to provide\n * consistent ways of querying the component during tests.\n */\nexport class ChatMessagePageObject {\n public constructor(protected readonly element: ChatMessageElement) {}\n\n public getRenderedText(): string {\n const assigned = this.getContentSlot()?.assignedNodes({ flatten: true }) ?? [];\n return assigned\n .map(node => node.textContent ?? '')\n .join('')\n .trim();\n }\n\n public isContentRendered(): boolean {\n return this.getRenderedText().length > 0;\n }\n\n public isScrollAnchor(): boolean {\n return this.element.messageInternals.isScrollAnchor;\n }\n\n private getContentSlot(): HTMLSlotElement | null {\n return (\n this.element.shadowRoot?.querySelector<HTMLSlotElement>(\n '.message-content slot'\n ) ?? null\n );\n }\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ni/spright-components",
3
- "version": "6.20.3",
3
+ "version": "6.21.0",
4
4
  "description": "NI Spright Components",
5
5
  "scripts": {
6
6
  "build": "npm run build:components && npm run build:cem",
@@ -42,7 +42,7 @@
42
42
  "@ni/fast-element": "^10.1.1",
43
43
  "@ni/fast-foundation": "^10.2.3",
44
44
  "@ni/fast-web-utilities": "^10.0.0",
45
- "@ni/nimble-components": "^35.10.0",
45
+ "@ni/nimble-components": "^35.11.0",
46
46
  "@ni/nimble-tokens": "^8.18.1",
47
47
  "tslib": "^2.2.0"
48
48
  },