Skip to content

Commit 31d8f07

Browse files
authored
Merge branch 'master' into mpopov/calendar/refactoring
2 parents c415982 + a1cdbf3 commit 31d8f07

File tree

21 files changed

+819
-485
lines changed

21 files changed

+819
-485
lines changed

package-lock.json

Lines changed: 588 additions & 229 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -60,14 +60,14 @@
6060
"lit": "^3.3.1"
6161
},
6262
"devDependencies": {
63-
"@biomejs/biome": "~2.3.2",
64-
"@custom-elements-manifest/analyzer": "^0.10.10",
63+
"@biomejs/biome": "~2.3.3",
64+
"@custom-elements-manifest/analyzer": "^0.11.0",
6565
"@igniteui/material-icons-extended": "^3.1.0",
6666
"@open-wc/testing": "^4.0.0",
67-
"@storybook/addon-a11y": "^10.0.1",
68-
"@storybook/addon-docs": "^10.0.1",
69-
"@storybook/addon-links": "^10.0.1",
70-
"@storybook/web-components-vite": "^10.0.1",
67+
"@storybook/addon-a11y": "^10.0.4",
68+
"@storybook/addon-docs": "^10.0.4",
69+
"@storybook/addon-links": "^10.0.4",
70+
"@storybook/web-components-vite": "^10.0.4",
7171
"@types/mocha": "^10.0.10",
7272
"@web/dev-server-esbuild": "^1.0.4",
7373
"@web/test-runner": "^0.20.2",
@@ -91,10 +91,10 @@
9191
"playwright": "^1.56.1",
9292
"postcss": "^8.5.6",
9393
"prettier": "^3.6.2",
94-
"rimraf": "^6.0.1",
95-
"sass-embedded": "~1.93.2",
94+
"rimraf": "^6.1.0",
95+
"sass-embedded": "~1.93.3",
9696
"sinon": "^21.0.0",
97-
"storybook": "^10.0.1",
97+
"storybook": "^10.0.4",
9898
"stylelint": "^16.25.0",
9999
"stylelint-config-standard-scss": "^16.0.0",
100100
"stylelint-prettier": "^5.0.3",
@@ -104,7 +104,7 @@
104104
"typedoc": "~0.28.14",
105105
"typedoc-plugin-localization": "^3.1.0",
106106
"typescript": "^5.9.3",
107-
"vite": "^7.1.12"
107+
"vite": "^7.2.0"
108108
},
109109
"peerDependencies": {
110110
"dompurify": "^3.3.0",

src/components/chat/chat-input.ts

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { consume } from '@lit/context';
1+
import { ContextConsumer, consume } from '@lit/context';
22
import { html, LitElement, nothing } from 'lit';
33
import { query, state } from 'lit/decorators.js';
44
import { cache } from 'lit/directives/cache.js';
@@ -24,7 +24,12 @@ import type {
2424
ChatTemplateRenderer,
2525
IgcChatMessageAttachment,
2626
} from './types.js';
27-
import { getChatAcceptedFiles, getIconName } from './utils.js';
27+
import {
28+
addAdoptedStylesController,
29+
type ChatAcceptedFileTypes,
30+
getChatAcceptedFiles,
31+
getIconName,
32+
} from './utils.js';
2833

2934
type DefaultInputRenderers = {
3035
input: ChatTemplateRenderer<ChatInputRenderContext>;
@@ -95,33 +100,53 @@ export default class IgcChatInputComponent extends LitElement {
95100
private _userLastTypeTime = Date.now();
96101
private _typingTimeout = 0;
97102

98-
@consume({ context: chatContext, subscribe: true })
99-
private readonly _state!: ChatState;
103+
private readonly _adoptedStyles = addAdoptedStylesController(this);
104+
105+
private readonly _stateChanged = () => {
106+
this._adoptedStyles.shouldAdoptStyles(
107+
!!this._state.options?.adoptRootStyles &&
108+
!this._adoptedStyles.hasAdoptedStyles
109+
);
110+
};
111+
112+
private readonly _stateConsumer = new ContextConsumer(this, {
113+
context: chatContext,
114+
callback: this._stateChanged,
115+
subscribe: true,
116+
});
100117

101118
@consume({ context: chatUserInputContext, subscribe: true })
102119
private readonly _userInputState!: ChatState;
103120

104121
@query(IgcTextareaComponent.tagName)
105-
private readonly _textInputElement!: IgcTextareaComponent;
122+
private readonly _textInputElement?: IgcTextareaComponent;
106123

107124
@query('#input_attachments')
108-
protected readonly _fileInput!: HTMLInputElement;
125+
protected readonly _fileInput?: HTMLInputElement;
109126

110127
@state()
111128
private _parts = { 'input-container': true, dragging: false };
112129

113-
private get _acceptedTypes() {
130+
private get _state(): ChatState {
131+
return this._stateConsumer.value!;
132+
}
133+
134+
private get _acceptedTypes(): ChatAcceptedFileTypes | null {
114135
return this._state.acceptedFileTypes;
115136
}
116137

117138
constructor() {
118139
super();
119-
addThemingController(this, all);
140+
addThemingController(this, all, { themeChange: this._adoptPageStyles });
120141
}
121142

122143
/** @internal */
123144
public focusInput(): void {
124-
this._textInputElement.focus();
145+
this._textInputElement?.focus();
146+
}
147+
148+
private _adoptPageStyles(): void {
149+
this._adoptedStyles.shouldAdoptStyles(this._adoptedStyles.hasAdoptedStyles);
125150
}
126151

127152
private _getRenderer<U extends keyof DefaultInputRenderers>(
@@ -204,7 +229,7 @@ export default class IgcChatInputComponent extends LitElement {
204229
}
205230

206231
private _handleFileInputClick(): void {
207-
this._fileInput.showPicker();
232+
this._fileInput?.showPicker();
208233
}
209234

210235
private _handleFocusState(event: FocusEvent): void {

src/components/chat/chat-message.ts

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { consume } from '@lit/context';
1+
import { ContextConsumer } from '@lit/context';
22
import { html, LitElement, nothing } from 'lit';
33
import { property } from 'lit/decorators.js';
44
import { cache } from 'lit/directives/cache.js';
@@ -19,7 +19,7 @@ import type {
1919
ChatTemplateRenderer,
2020
IgcChatMessage,
2121
} from './types.js';
22-
import { chatMessageAdoptPageStyles } from './utils.js';
22+
import { addAdoptedStylesController } from './utils.js';
2323

2424
const LIKE_INACTIVE = 'thumb_up_inactive';
2525
const LIKE_ACTIVE = 'thumb_up_active';
@@ -66,15 +66,31 @@ export default class IgcChatMessageComponent extends LitElement {
6666
);
6767
}
6868

69+
private readonly _adoptedStyles = addAdoptedStylesController(this);
70+
6971
private readonly _defaults = Object.freeze<DefaultMessageRenderers>({
7072
messageHeader: () => this._renderHeader(),
7173
messageContent: () => this._renderContent(),
7274
messageAttachments: () => this._renderAttachments(),
7375
messageActions: () => this._renderActions(),
7476
});
7577

76-
@consume({ context: chatContext, subscribe: true })
77-
private readonly _state!: ChatState;
78+
private readonly _stateChanged = () => {
79+
this._adoptedStyles.shouldAdoptStyles(
80+
!!this._state.options?.adoptRootStyles &&
81+
!this._adoptedStyles.hasAdoptedStyles
82+
);
83+
};
84+
85+
private readonly _stateConsumer = new ContextConsumer(this, {
86+
context: chatContext,
87+
callback: this._stateChanged,
88+
subscribe: true,
89+
});
90+
91+
private get _state(): ChatState {
92+
return this._stateConsumer.value!;
93+
}
7894

7995
/**
8096
* The chat message to render.
@@ -84,13 +100,11 @@ export default class IgcChatMessageComponent extends LitElement {
84100

85101
constructor() {
86102
super();
87-
addThemingController(this, all);
103+
addThemingController(this, all, { themeChange: this._adoptPageStyles });
88104
}
89105

90-
protected override firstUpdated(): void {
91-
if (this._state.options?.adoptRootStyles) {
92-
chatMessageAdoptPageStyles(this);
93-
}
106+
private _adoptPageStyles(): void {
107+
this._adoptedStyles.shouldAdoptStyles(this._adoptedStyles.hasAdoptedStyles);
94108
}
95109

96110
private _getRenderer(name: keyof DefaultMessageRenderers) {

src/components/chat/chat.spec.ts

Lines changed: 52 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { elementUpdated, expect, fixture } from '@open-wc/testing';
22
import { html, nothing } from 'lit';
33
import { spy, stub, useFakeTimers } from 'sinon';
4+
import { configureTheme } from '../../theming/config.js';
45
import type IgcIconButtonComponent from '../button/icon-button.js';
56
import IgcChipComponent from '../chip/chip.js';
67
import { enterKey, tabKey } from '../common/controllers/key-bindings.js';
@@ -22,7 +23,12 @@ import IgcChatComponent from './chat.js';
2223
import IgcChatInputComponent from './chat-input.js';
2324
import IgcChatMessageComponent from './chat-message.js';
2425
import IgcMessageAttachmentsComponent from './message-attachments.js';
25-
import type { IgcChatMessage, IgcChatMessageAttachment } from './types.js';
26+
import type {
27+
ChatMessageRenderContext,
28+
IgcChatMessage,
29+
IgcChatMessageAttachment,
30+
IgcChatOptions,
31+
} from './types.js';
2632

2733
describe('Chat', () => {
2834
before(() => {
@@ -1026,7 +1032,30 @@ describe('Chat', () => {
10261032
});
10271033

10281034
describe('adoptRootStyles behavior', () => {
1029-
const customStyles = 'custom-background';
1035+
let chat: IgcChatComponent;
1036+
1037+
const renderer = ({ message }: ChatMessageRenderContext) =>
1038+
html`<div class="custom-background">${message.text}</div>`;
1039+
1040+
async function createAdoptedStylesChat(options: IgcChatOptions) {
1041+
chat = await fixture(html`
1042+
<igc-chat
1043+
.messages=${[{ id: 'id', sender: 'bot', text: 'Hello' }]}
1044+
.options=${{ renderers: { messageContent: renderer }, ...options }}
1045+
></igc-chat>
1046+
`);
1047+
}
1048+
1049+
function verifyCustomStyles(state: boolean) {
1050+
const { messages } = getChatDOM(chat);
1051+
const { backgroundColor } = getComputedStyle(
1052+
getChatMessageDOM(first(messages)).content.querySelector(
1053+
'.custom-background'
1054+
)!
1055+
);
1056+
1057+
expect(backgroundColor === 'rgb(255, 0, 0)').to.equal(state);
1058+
}
10301059

10311060
beforeEach(async () => {
10321061
const styles = document.createElement('style');
@@ -1039,48 +1068,36 @@ describe('Chat', () => {
10391068
document.head.append(styles);
10401069
});
10411070

1042-
it('correctly applies `adoptRootStyles` when set', async () => {
1043-
chat.options = {
1044-
adoptRootStyles: true,
1045-
renderers: {
1046-
messageContent: ({ message }) =>
1047-
html`<div class=${customStyles}>${message.text}</div>`,
1048-
},
1049-
};
1050-
chat.messages = [{ id: 'id', sender: 'bot', text: 'Hello' }];
1071+
afterEach(() => {
1072+
document.head.querySelector('#adopt-styles-test')?.remove();
1073+
});
10511074

1075+
it('correctly applies `adoptRootStyles` when set', async () => {
1076+
await createAdoptedStylesChat({ adoptRootStyles: true });
10521077
await elementUpdated(chat);
1053-
1054-
const { messages } = getChatDOM(chat);
1055-
expect(
1056-
getComputedStyle(
1057-
getChatMessageDOM(first(messages)).content.querySelector(
1058-
`.${customStyles}`
1059-
)!
1060-
).backgroundColor
1061-
).equal('rgb(255, 0, 0)');
1078+
verifyCustomStyles(true);
10621079
});
10631080

10641081
it('skips `adoptRootStyles` when not set', async () => {
1065-
chat.options = {
1066-
renderers: {
1067-
messageContent: ({ message }) =>
1068-
html`<div class=${customStyles}>${message.text}</div>`,
1069-
},
1070-
};
1082+
await createAdoptedStylesChat({ adoptRootStyles: false });
1083+
await elementUpdated(chat);
1084+
verifyCustomStyles(false);
1085+
});
1086+
1087+
it('correctly reapplies `adoptRootStyles` when set and the theme is changed', async () => {
1088+
await createAdoptedStylesChat({ adoptRootStyles: true });
1089+
await elementUpdated(chat);
1090+
verifyCustomStyles(true);
10711091

1072-
chat.messages = [{ id: 'id', sender: 'bot', text: 'Hello' }];
1092+
// Change the theme
1093+
configureTheme('material');
10731094

10741095
await elementUpdated(chat);
1096+
verifyCustomStyles(true);
10751097

1076-
const { messages } = getChatDOM(chat);
1077-
expect(
1078-
getComputedStyle(
1079-
getChatMessageDOM(first(messages)).content.querySelector(
1080-
`.${customStyles}`
1081-
)!
1082-
).backgroundColor
1083-
).not.equal('rgb(255, 0, 0)');
1098+
configureTheme('bootstrap');
1099+
await elementUpdated(chat);
1100+
verifyCustomStyles(true);
10841101
});
10851102
});
10861103
});

src/components/chat/chat.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,8 @@ const Slots = setSlots(
105105
'suggestions',
106106
'suggestions-actions',
107107
'suggestion',
108-
'empty-state'
108+
'empty-state',
109+
'typing-indicator'
109110
);
110111

111112
/**
@@ -133,6 +134,7 @@ const Slots = setSlots(
133134
* @slot suggestions-actions - Slot for rendering additional actions.
134135
* @slot suggestion - Slot for rendering a single suggestion item.
135136
* @slot empty-state - Slot shown when there are no messages.
137+
* @slot typing-indicator - Slot for the "is typing" indicator.
136138
*
137139
* @csspart chat-container - Styles the main chat container.
138140
* @csspart header - Styles the chat header container.
@@ -413,7 +415,9 @@ export default class IgcChatComponent extends EventEmitterMixin<
413415
${this._state.options?.isTyping
414416
? html`
415417
<div part="typing-indicator">
416-
${this._getRenderer('typingIndicator')(ctx)}
418+
<slot name="typing-indicator"
419+
>${this._getRenderer('typingIndicator')(ctx)}</slot
420+
>
417421
</div>
418422
`
419423
: nothing}

src/components/chat/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,8 @@ export interface ChatRenderers {
234234
messageHeader?: ChatTemplateRenderer<ChatMessageRenderContext>;
235235
/**
236236
* Custom renderer for the "is typing" indicator.
237+
*
238+
* @deprecated since 6.4.0. Use the `typing-indicator` slot.
237239
*/
238240
typingIndicator?: ChatTemplateRenderer<ChatRenderContext>;
239241
/**

0 commit comments

Comments
 (0)