Skip to content

Commit e44cb42

Browse files
authored
Merge branch 'master' into simeonoff/bump-typedoc
2 parents 806f017 + b2c488a commit e44cb42

22 files changed

+515
-410
lines changed

.storybook/preview.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ export default {
164164
},
165165
parameters: {
166166
backgrounds: {
167-
disable: true,
167+
disabled: true,
168168
},
169169
},
170170
decorators: [themeProvider, withActions, localeProvider],

package-lock.json

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

package.json

Lines changed: 9 additions & 9 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.2.6",
63+
"@biomejs/biome": "~2.3.2",
6464
"@custom-elements-manifest/analyzer": "^0.10.10",
6565
"@igniteui/material-icons-extended": "^3.1.0",
6666
"@open-wc/testing": "^4.0.0",
67-
"@storybook/addon-a11y": "^9.1.13",
68-
"@storybook/addon-docs": "^9.1.13",
69-
"@storybook/addon-links": "^9.1.13",
70-
"@storybook/web-components-vite": "^9.1.13",
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",
7171
"@types/mocha": "^10.0.10",
7272
"@web/dev-server-esbuild": "^1.0.4",
7373
"@web/test-runner": "^0.20.2",
@@ -84,7 +84,7 @@
8484
"igniteui-i18n-resources": "0.6.0-alpha.4",
8585
"igniteui-theming": "^22.1.0",
8686
"keep-a-changelog": "^2.7.1",
87-
"lint-staged": "^16.2.5",
87+
"lint-staged": "^16.2.6",
8888
"lit-analyzer": "^2.0.3",
8989
"madge": "^8.0.0",
9090
"node-watch": "^0.7.4",
@@ -94,7 +94,7 @@
9494
"rimraf": "^6.0.1",
9595
"sass-embedded": "~1.93.2",
9696
"sinon": "^21.0.0",
97-
"storybook": "^9.1.13",
97+
"storybook": "^10.0.1",
9898
"stylelint": "^16.25.0",
9999
"stylelint-config-standard-scss": "^16.0.0",
100100
"stylelint-prettier": "^5.0.3",
@@ -104,13 +104,13 @@
104104
"typedoc": "~0.28.14",
105105
"typedoc-plugin-localization": "^3.1.0",
106106
"typescript": "^5.8.3",
107-
"vite": "^7.1.11"
107+
"vite": "^7.1.12"
108108
},
109109
"peerDependencies": {
110110
"dompurify": "^3.3.0",
111111
"marked": "^16.4.1",
112112
"marked-shiki": "^1.2.1",
113-
"shiki": "^3.13.0"
113+
"shiki": "^3.14.0"
114114
},
115115
"browserslist": [
116116
"defaults"

scripts/report.mjs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
11
import { stdout } from 'node:process';
2-
import { format } from 'node:util';
2+
import { format, styleText } from 'node:util';
33

44
export default {
5-
error: (s) => console.error('\x1b[31m%s\x1b[0m', s),
6-
success: (s) => console.info('\x1b[32m%s\x1b[0m', s),
7-
warn: (s) => console.warn('\x1b[33m%s\x1b[0m', s),
8-
info: (s) => console.info('\x1b[36m%s\x1b[0m', s),
5+
error: (s) => console.error(styleText('red', s)),
6+
success: (s) => console.info(styleText('green', s)),
7+
warn: (s) => console.warn(styleText('yellow', s)),
8+
info: (s) => console.info(styleText('cyan', s)),
99

1010
stdout: {
1111
clearLine: () => {
1212
stdout.clearLine(0);
1313
stdout.cursorTo(0);
1414
},
15-
success: (s) => stdout.write(format('\x1b[32m%s\x1b[0m', s)),
16-
warn: (s) => stdout.write(format('\x1b[33m%s\x1b[0m', s)),
17-
info: (s) => stdout.write(format('\x1b[36m%s\x1b[0m', s)),
15+
success: (s) => stdout.write(format(styleText('green', s))),
16+
warn: (s) => stdout.write(format(styleText('yellow', s))),
17+
info: (s) => stdout.write(format(styleText('cyan', s))),
1818
},
1919
};

src/components/chat/chat-input.ts

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { addThemingController } from '../../theming/theming-controller.js';
88
import IgcIconButtonComponent from '../button/icon-button.js';
99
import IgcChipComponent from '../chip/chip.js';
1010
import { chatContext, chatUserInputContext } from '../common/context.js';
11-
import { enterKey } from '../common/controllers/key-bindings.js';
11+
import { enterKey, tabKey } from '../common/controllers/key-bindings.js';
1212
import { registerComponent } from '../common/definitions/register.js';
1313
import { partMap } from '../common/part-map.js';
1414
import { bindIf, hasFiles, isEmpty, trimmedHtml } from '../common/util.js';
@@ -91,6 +91,10 @@ export default class IgcChatInputComponent extends LitElement {
9191
sendButton: () => this._renderSendButton(),
9292
});
9393

94+
private _userIsTyping = false;
95+
private _userLastTypeTime = Date.now();
96+
private _typingTimeout = 0;
97+
9498
@consume({ context: chatContext, subscribe: true })
9599
private readonly _state!: ChatState;
96100

@@ -148,6 +152,11 @@ export default class IgcChatInputComponent extends LitElement {
148152
this.focusInput();
149153
}
150154

155+
private _setTypingStateAndEmit(state: boolean): void {
156+
this._userIsTyping = state;
157+
this._userInputState.emitUserTypingState(state);
158+
}
159+
151160
private _handleAttachmentRemoved(attachment: IgcChatMessageAttachment): void {
152161
const current = this._userInputState.inputAttachments;
153162

@@ -160,16 +169,38 @@ export default class IgcChatInputComponent extends LitElement {
160169
}
161170

162171
private _handleKeydown(event: KeyboardEvent): void {
163-
const isSendRequest =
164-
event.key.toLowerCase() === enterKey.toLowerCase() && !event.shiftKey;
172+
this._userLastTypeTime = Date.now();
173+
const isEnterKey = event.key.toLowerCase() === enterKey.toLowerCase();
174+
const isTab = event.key.toLocaleLowerCase() === tabKey.toLowerCase();
165175

166-
if (isSendRequest) {
176+
if (isTab && !this._userIsTyping) {
177+
return;
178+
}
179+
180+
if (isEnterKey && !event.shiftKey) {
167181
event.preventDefault();
168182
this._sendMessage();
169-
} else {
170-
// TODO:
171-
this._state.handleKeyDown(event);
183+
184+
if (this._userIsTyping) {
185+
clearTimeout(this._typingTimeout);
186+
this._setTypingStateAndEmit(false);
187+
}
188+
189+
return;
172190
}
191+
192+
clearTimeout(this._typingTimeout);
193+
const delay = this._state.stopTypingDelay;
194+
195+
if (!this._userIsTyping) {
196+
this._setTypingStateAndEmit(true);
197+
}
198+
199+
this._typingTimeout = setTimeout(() => {
200+
if (this._userIsTyping && this._userLastTypeTime + delay <= Date.now()) {
201+
this._setTypingStateAndEmit(false);
202+
}
203+
}, delay);
173204
}
174205

175206
private _handleFileInputClick(): void {
@@ -238,6 +269,7 @@ export default class IgcChatInputComponent extends LitElement {
238269
this._state.attachFilesWithEvent(Array.from(input.files!));
239270
}
240271
}
272+
241273
/**
242274
* Default attachments area template used when no custom template is provided.
243275
* Renders the list of input attachments as chips.

src/components/chat/chat-state.ts

Lines changed: 5 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,6 @@ export class ChatState {
4646
*/
4747
private _acceptedTypesCache: ChatAcceptedFileTypes | null = null;
4848

49-
private _isTyping = false;
50-
private _lastTyped = Date.now();
51-
5249
public resourceStrings = IgcChatResourceStringEN;
5350

5451
//#endregion
@@ -217,6 +214,11 @@ export class ChatState {
217214
return this._host.emitEvent('igcMessageReact', { detail: reaction });
218215
}
219216

217+
/** @internal */
218+
public emitUserTypingState(state: boolean): boolean {
219+
return this._host.emitEvent('igcTypingChange', { detail: state });
220+
}
221+
220222
/**
221223
* @internal
222224
*/
@@ -326,29 +328,5 @@ export class ChatState {
326328
}
327329
}
328330

329-
public handleKeyDown = (_: KeyboardEvent) => {
330-
this._lastTyped = Date.now();
331-
if (!this._isTyping) {
332-
this.emitEvent('igcTypingChange', {
333-
detail: { isTyping: true },
334-
});
335-
this._isTyping = true;
336-
337-
const stopTypingDelay = this.stopTypingDelay;
338-
setTimeout(() => {
339-
if (
340-
this._isTyping &&
341-
stopTypingDelay &&
342-
this._lastTyped + stopTypingDelay < Date.now()
343-
) {
344-
this.emitEvent('igcTypingChange', {
345-
detail: { isTyping: false },
346-
});
347-
this._isTyping = false;
348-
}
349-
}, stopTypingDelay);
350-
}
351-
};
352-
353331
//#endregion
354332
}

src/components/chat/chat.spec.ts

Lines changed: 53 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,15 @@ import { html, nothing } from 'lit';
33
import { spy, stub, useFakeTimers } from 'sinon';
44
import type IgcIconButtonComponent from '../button/icon-button.js';
55
import IgcChipComponent from '../chip/chip.js';
6-
import { enterKey } from '../common/controllers/key-bindings.js';
6+
import { enterKey, tabKey } from '../common/controllers/key-bindings.js';
77
import { defineComponents } from '../common/definitions/defineComponents.js';
88
import { first, last } from '../common/util.js';
99
import {
1010
isFocused,
1111
simulateBlur,
1212
simulateClick,
1313
simulateFocus,
14+
simulateInput,
1415
simulateKeyboard,
1516
} from '../common/utils.spec.js';
1617
import { simulateFileUpload } from '../file-input/file-input.spec.js';
@@ -888,21 +889,69 @@ describe('Chat', () => {
888889
const textArea = getChatDOM(chat).input.textarea;
889890

890891
chat.options = { stopTypingDelay: 2500 };
891-
simulateKeyboard(textArea, 'a');
892+
simulateKeyboard(textArea, 'a', 15);
892893
await elementUpdated(chat);
893894

894895
expect(eventSpy).calledWith('igcTypingChange');
895-
expect(eventSpy.firstCall.args[1]?.detail).to.eql({ isTyping: true });
896+
expect(eventSpy.firstCall.args[1]?.detail).to.be.true;
896897

897898
clock.setSystemTime(2501);
898899
await clock.runAllAsync();
899900

900901
expect(eventSpy).calledWith('igcTypingChange');
901-
expect(eventSpy.lastCall.args[1]?.detail).to.eql({ isTyping: false });
902+
expect(eventSpy.lastCall.args[1]?.detail).to.be.false;
902903

903904
clock.restore();
904905
});
905906

907+
it('emits igcTypingChange after sending a message', async () => {
908+
const eventSpy = spy(chat, 'emitEvent');
909+
const textArea = getChatDOM(chat).input.textarea;
910+
const internalInput = textArea.renderRoot.querySelector('textarea')!;
911+
912+
chat.options = { stopTypingDelay: 2500 };
913+
914+
// Simulate typing some text and the event sequence following after sending a message
915+
916+
// Fires igcTypingChange
917+
simulateKeyboard(textArea, 'a', 15);
918+
await elementUpdated(textArea);
919+
920+
// Fires igcInputChange
921+
simulateInput(internalInput, { value: 'a'.repeat(15) });
922+
await elementUpdated(textArea);
923+
924+
// Fires igcMessageCreated -> igcTypingChange -> igcInputFocus since sending a message refocuses
925+
// the textarea
926+
simulateKeyboard(textArea, enterKey);
927+
await elementUpdated(chat);
928+
929+
const expectedEventSequence = [
930+
'igcTypingChange',
931+
'igcInputChange',
932+
'igcMessageCreated',
933+
'igcTypingChange',
934+
'igcInputFocus',
935+
];
936+
937+
for (const [idx, event] of expectedEventSequence.entries()) {
938+
expect(eventSpy.getCall(idx).firstArg).to.equal(event);
939+
}
940+
});
941+
942+
it('should not emit igcTypingChange on Tab key', async () => {
943+
const eventSpy = spy(chat, 'emitEvent');
944+
const textArea = getChatDOM(chat).input.textarea;
945+
const internalInput = textArea.renderRoot.querySelector('textarea')!;
946+
947+
chat.options = { stopTypingDelay: 2500 };
948+
949+
simulateKeyboard(internalInput, tabKey);
950+
await elementUpdated(chat);
951+
952+
expect(eventSpy.getCalls()).is.empty;
953+
});
954+
906955
it('emits igcInputFocus', async () => {
907956
const eventSpy = spy(chat, 'emitEvent');
908957

src/components/chat/utils.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,6 @@ export function isImageAttachment(
110110
);
111111
}
112112

113-
// REVIEW: Maybe put that behind a configuration flag as this is nasty.
114113
export function chatMessageAdoptPageStyles(
115114
message: IgcChatMessageComponent
116115
): void {
@@ -120,6 +119,10 @@ export function chatMessageAdoptPageStyles(
120119
try {
121120
const constructed = new CSSStyleSheet();
122121
for (const rule of sheet.cssRules) {
122+
// https://drafts.csswg.org/cssom/#dom-cssstylesheet-insertrule:~:text=If%20parsed%20rule%20is%20an%20%40import%20rule
123+
if (rule.cssText.startsWith('@import')) {
124+
continue;
125+
}
123126
constructed.insertRule(rule.cssText);
124127
}
125128
sheets.push(constructed);

src/components/checkbox/themes/shared/checkbox/checkbox.bootstrap.scss

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -53,14 +53,9 @@ $theme: $bootstrap;
5353
}
5454

5555
:host(:hover) {
56-
[part~='control']::after {
56+
[part='control']::after {
5757
box-shadow: inset 0 0 0 rem(1px) var-get($theme, 'empty-color-hover');
5858
}
59-
60-
[part='control checked']::after {
61-
background: var-get($theme, 'fill-color-hover');
62-
box-shadow: inset 0 0 0 rem(1px) var-get($theme, 'fill-color-hover');
63-
}
6459
}
6560

6661
:host(:focus-visible) ,

src/components/checkbox/themes/shared/checkbox/checkbox.common.scss

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,26 @@
11
@use 'styles/utilities' as *;
22
@use '../../light/checkbox/themes' as *;
33

4-
$theme: $base;
4+
$theme: $material;
55

66
[part~='label'] {
77
@include type-style('body-2');
88

99
color: var-get($theme, 'label-color');
1010
}
1111

12+
:host(:not([disabled], :state(ig-invalid)):hover),
13+
:host(:not([disabled], :state(ig-invalid))[indeterminate]:hover) {
14+
[part~='label'] {
15+
color: var-get($theme, 'label-color-hover');
16+
}
17+
18+
[part='control checked']::after {
19+
background: var-get($theme, 'fill-color-hover');
20+
box-shadow: inset 0 0 0 rem(1px) var-get($theme, 'fill-color-hover');
21+
}
22+
}
23+
1224
[part~='control'] {
1325
background: var-get($theme, 'empty-fill-color');
1426
}

0 commit comments

Comments
 (0)