Skip to content
Merged
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ Notes: web developers are advised to use [`~` (tilde range)](https://github.com/
- Resolved [#2661](https://github.com/microsoft/BotFramework-WebChat/issues/2661) and [#5352](https://github.com/microsoft/BotFramework-WebChat/issues/5352). Added speech recognition continuous mode with barge-in support, in PR [#5426](https://github.com/microsoft/BotFramework-WebChat/pull/5426), by [@RushikeshGavali](https://github.com/RushikeshGavali) and [@compulim](https://github.com/compulim)
- Set `styleOptions.speechRecognitionContinuous` to `true` with a Web Speech API provider with continuous mode support
- Added support of [contentless activity in livestream](https://github.com/microsoft/BotFramework-WebChat/blob/main/docs/LIVESTREAMING.md#scenario-3-interim-activities-with-no-content), in PR [#5430](https://github.com/microsoft/BotFramework-WebChat/pull/5430), by [@compulim](https://github.com/compulim)
- Added sliding dots typing indicator in Fluent theme, in PR [#5447](https://github.com/microsoft/BotFramework-WebChat/pull/5447), by [@compulim](https://github.com/compulim)

### Changed

Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
38 changes: 21 additions & 17 deletions __tests__/html/fluentTheme/side-by-side.wide.dark.html
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@
Fluent: { FluentProvider, createDarkTheme }
} = window; // Imports in UMD fashion.

await host.sendDevToolsCommand('Emulation.setEmulatedMedia', {
features: [{ name: 'prefers-reduced-motion', value: 'reduce' }]
});

await host.windowSize(1460, 700, document.getElementById('webchat'));

let timestampStart = new Date(2020, 7, 9).getTime();
Expand Down Expand Up @@ -104,17 +108,17 @@
const waveSvg = `data:image/svg+xml;utf8,${encodeURIComponent(`
<svg viewBox="0 50 400 100" xmlns="http://www.w3.org/2000/svg">
<!-- Primary Wave -->
<path d="M0,100 C50,50 100,150 150,100 C200,50 250,150 300,100 C350,50 400,150 400,100"
<path d="M0,100 C50,50 100,150 150,100 C200,50 250,150 300,100 C350,50 400,150 400,100"
stroke="#3B82F6" fill="none" stroke-width="2" opacity="0.5"/>

<!-- Second Harmonic -->
<path d="M0,100 C25,75 50,125 75,100 C100,75 125,125 150,100 C175,75 200,125 225,100 C250,75 275,125 300,100 C325,75 350,125 375,100 C400,75 400,125 400,100"
<path d="M0,100 C25,75 50,125 75,100 C100,75 125,125 150,100 C175,75 200,125 225,100 C250,75 275,125 300,100 C325,75 350,125 375,100 C400,75 400,125 400,100"
stroke="#10B981" fill="none" stroke-width="2" opacity="0.5"/>

<!-- Combined Wave -->
<path d="M0,100 C40,30 80,170 120,100 C160,30 200,170 240,100 C280,30 320,170 360,100 C380,65 400,135 400,100"
<path d="M0,100 C40,30 80,170 120,100 C160,30 200,170 240,100 C280,30 320,170 360,100 C380,65 400,135 400,100"
stroke="#EF4444" fill="none" stroke-width="3"/>

<!-- Grid Lines -->
<line x1="0" y1="100" x2="400" y2="100" stroke="#CBD5E1" stroke-width="0.5" stroke-dasharray="4"/>
<line x1="100" y1="0" x2="100" y2="200" stroke="#CBD5E1" stroke-width="0.5" stroke-dasharray="4"/>
Expand Down Expand Up @@ -576,35 +580,35 @@
"""Create a beautiful visualization of sine waves with different frequencies."""
# Generate time points
t = np.linspace(0, 10, 1000)

# Create waves with different frequencies and phases
wave1 = np.sin(t)
wave2 = 0.5 * np.sin(2 * t + np.pi/4)
wave3 = 0.3 * np.sin(3 * t + np.pi/3)

# Combine waves
combined = wave1 + wave2 + wave3

# Create a stylish plot
plt.style.use('seaborn-darkgrid')
plt.figure(figsize=(12, 8))

# Plot individual waves
plt.plot(t, wave1, label='Primary Wave', alpha=0.5)
plt.plot(t, wave2, label='Second Harmonic', alpha=0.5)
plt.plot(t, wave3, label='Third Harmonic', alpha=0.5)

# Plot combined wave with a thicker line
plt.plot(t, combined, 'r-',
label='Combined Wave',
plt.plot(t, combined, 'r-',
label='Combined Wave',
linewidth=2)

plt.title('Harmonic Wave Composition', fontsize=14)
plt.xlabel('Time', fontsize=12)
plt.ylabel('Amplitude', fontsize=12)
plt.legend()
plt.grid(True, alpha=0.3)

# Show the plot
plt.tight_layout()
plt.show()
Expand Down Expand Up @@ -639,7 +643,7 @@
{
"@type": "LikeAction",
actionStatus: "CompletedActionStatus",
target: {
target: {
"@type": "EntryPoint",
urlTemplate: "ms-directline://postback?interaction=like"
}
Expand Down Expand Up @@ -681,7 +685,7 @@
{
"@type": "LikeAction",
actionStatus: "PotentialActionStatus",
target: {
target: {
"@type": "EntryPoint",
urlTemplate: "ms-directline://postback?interaction=like"
}
Expand Down
38 changes: 21 additions & 17 deletions __tests__/html/fluentTheme/side-by-side.wide.html
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,10 @@
WebChat: { FluentThemeProvider, ReactWebChat }
} = window; // Imports in UMD fashion.

await host.sendDevToolsCommand('Emulation.setEmulatedMedia', {
features: [{ name: 'prefers-reduced-motion', value: 'reduce' }]
});

await host.windowSize(1460, 700, document.getElementById('webchat'));

let timestampStart = new Date(2020, 7, 9).getTime();
Expand Down Expand Up @@ -114,17 +118,17 @@
const waveSvg = `data:image/svg+xml;utf8,${encodeURIComponent(`
<svg viewBox="0 50 400 100" xmlns="http://www.w3.org/2000/svg">
<!-- Primary Wave -->
<path d="M0,100 C50,50 100,150 150,100 C200,50 250,150 300,100 C350,50 400,150 400,100"
<path d="M0,100 C50,50 100,150 150,100 C200,50 250,150 300,100 C350,50 400,150 400,100"
stroke="#3B82F6" fill="none" stroke-width="2" opacity="0.5"/>

<!-- Second Harmonic -->
<path d="M0,100 C25,75 50,125 75,100 C100,75 125,125 150,100 C175,75 200,125 225,100 C250,75 275,125 300,100 C325,75 350,125 375,100 C400,75 400,125 400,100"
<path d="M0,100 C25,75 50,125 75,100 C100,75 125,125 150,100 C175,75 200,125 225,100 C250,75 275,125 300,100 C325,75 350,125 375,100 C400,75 400,125 400,100"
stroke="#10B981" fill="none" stroke-width="2" opacity="0.5"/>

<!-- Combined Wave -->
<path d="M0,100 C40,30 80,170 120,100 C160,30 200,170 240,100 C280,30 320,170 360,100 C380,65 400,135 400,100"
<path d="M0,100 C40,30 80,170 120,100 C160,30 200,170 240,100 C280,30 320,170 360,100 C380,65 400,135 400,100"
stroke="#EF4444" fill="none" stroke-width="3"/>

<!-- Grid Lines -->
<line x1="0" y1="100" x2="400" y2="100" stroke="#CBD5E1" stroke-width="0.5" stroke-dasharray="4"/>
<line x1="100" y1="0" x2="100" y2="200" stroke="#CBD5E1" stroke-width="0.5" stroke-dasharray="4"/>
Expand Down Expand Up @@ -586,35 +590,35 @@
"""Create a beautiful visualization of sine waves with different frequencies."""
# Generate time points
t = np.linspace(0, 10, 1000)

# Create waves with different frequencies and phases
wave1 = np.sin(t)
wave2 = 0.5 * np.sin(2 * t + np.pi/4)
wave3 = 0.3 * np.sin(3 * t + np.pi/3)

# Combine waves
combined = wave1 + wave2 + wave3

# Create a stylish plot
plt.style.use('seaborn-darkgrid')
plt.figure(figsize=(12, 8))

# Plot individual waves
plt.plot(t, wave1, label='Primary Wave', alpha=0.5)
plt.plot(t, wave2, label='Second Harmonic', alpha=0.5)
plt.plot(t, wave3, label='Third Harmonic', alpha=0.5)

# Plot combined wave with a thicker line
plt.plot(t, combined, 'r-',
label='Combined Wave',
plt.plot(t, combined, 'r-',
label='Combined Wave',
linewidth=2)

plt.title('Harmonic Wave Composition', fontsize=14)
plt.xlabel('Time', fontsize=12)
plt.ylabel('Amplitude', fontsize=12)
plt.legend()
plt.grid(True, alpha=0.3)

# Show the plot
plt.tight_layout()
plt.show()
Expand Down Expand Up @@ -649,7 +653,7 @@
{
"@type": "LikeAction",
actionStatus: "CompletedActionStatus",
target: {
target: {
"@type": "EntryPoint",
urlTemplate: "ms-directline://postback?interaction=like"
}
Expand Down Expand Up @@ -691,7 +695,7 @@
{
"@type": "LikeAction",
actionStatus: "PotentialActionStatus",
target: {
target: {
"@type": "EntryPoint",
urlTemplate: "ms-directline://postback?interaction=like"
}
Expand Down
108 changes: 108 additions & 0 deletions __tests__/html2/fluentTheme/typingIndicator.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
<!doctype html>
<html lang="en-US">
<head>
<link href="/assets/index.css" rel="stylesheet" type="text/css" />
<script crossorigin="anonymous" src="https://unpkg.com/react@16.8.6/umd/react.production.min.js"></script>
<script crossorigin="anonymous" src="https://unpkg.com/react-dom@16.8.6/umd/react-dom.production.min.js"></script>
<script crossorigin="anonymous" src="/test-harness.js"></script>
<script crossorigin="anonymous" src="/test-page-object.js"></script>
<script crossorigin="anonymous" src="/__dist__/webchat-es5.js"></script>
<script crossorigin="anonymous" src="/__dist__/botframework-webchat-fluent-theme.production.min.js"></script>
</head>
<body>
<main id="webchat"></main>
<script type="importmap">
{
"imports": {
"@testduet/wait-for": "https://unpkg.com/@testduet/wait-for@main/dist/wait-for.mjs",
"jest-mock": "https://esm.sh/jest-mock",
"react-dictate-button/internal": "https://unpkg.com/react-dictate-button@main/dist/react-dictate-button.internal.mjs"
}
}
</script>
<script type="module">
import { waitFor } from '@testduet/wait-for';

const isLivestream = new URL(location).searchParams.has('livestream');

run(async function () {
const {
React: { createElement },
ReactDOM: { render },
WebChat: { FluentThemeProvider, ReactWebChat }
} = window; // Imports in UMD fashion.

await host.sendDevToolsCommand('Emulation.setEmulatedMedia', {
features: [{ name: 'prefers-reduced-motion', value: 'reduce' }]
});

const { directLine, store } = testHelpers.createDirectLineEmulator();

render(
createElement(FluentThemeProvider, {}, createElement(ReactWebChat, { directLine, store })),
document.getElementById('webchat')
);

await pageConditions.uiConnected();

// WHEN: Receive a bot message.
await directLine.emulateIncomingActivity({
from: { id: 'u-00001', name: 'Bot', role: 'bot' },
id: 'a-00001',
text: 'Hello, World!',
type: 'message'
});

// WHEN: Bot send either a contentless livestream or typing activity.
await directLine.emulateIncomingActivity({
...(isLivestream
? {
channelData: {
streamSequence: 1,
streamType: 'streaming'
}
}
: {}),
from: { id: 'u-00001', name: 'Bot', role: 'bot' },
id: 'a-00002',
type: 'typing'
});

// THEN: Should show typing indicator.
await waitFor(() => expect(pageElements.typingIndicator()).toBeTruthy());

// THEN: Should match snapshot.
await host.snapshot('local');

// ---

// WHEN: Bot send either a contentless livestream or typing activity.
await directLine.emulateIncomingActivity({
...(isLivestream
? {
channelData: {
streamId: 'a-00002',
streamType: 'final'
}
}
: {
channelData: {
webChat: {
styleOptions: { typingAnimationDuration: 0 }
}
}
}),
from: { id: 'u-00001', name: 'Bot', role: 'bot' },
id: 'a-00002',
type: 'typing'
});

// THEN: Should hide typing indicator.
await waitFor(() => expect(pageElements.typingIndicator()).toBeFalsy());

// THEN: Should match snapshot.
await host.snapshot('local');
});
</script>
</body>
</html>
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 9 additions & 0 deletions __tests__/html2/fluentTheme/typingIndicator.livestream.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<!doctype html>
<html>
<head>
<script>
location = './typingIndicator?livestream';
</script>
</head>
<body></body>
</html>
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
19 changes: 19 additions & 0 deletions docs/HOOKS.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ Following is the list of hooks supported by Web Chat API.
- [`useSendTypingIndicator`](#usesendtypingindicator)
- [`useSendStatusByActivityKey`](#usesendstatusbyactivitykey)
- [`useSetNotification`](#usesetnotification)
- [`useShouldReduceMotion`](#useshouldreducemotion)
- [`useShouldSpeakIncomingActivity`](#useshouldspeakincomingactivity)
- [`useStartDictate`](#usestartdictate)
- [`useStopDictate`](#usestopdictate)
Expand Down Expand Up @@ -1289,6 +1290,24 @@ The `message` field will be processed through an internal Markdown renderer. If

The toast UI will [debounce notifications](https://github.com/microsoft/BotFramework-WebChat/tree/main/docs/NOTIFICATION.md#postponing-changes-via-debounce) that update too frequently.

## `useShouldReduceMotion`

> Only available on `botframework-webchat-components` package.

> New in 4.19.0.

<!-- prettier-ignore-start -->
```js
useShouldReduceMotion(): readonly [boolean]
```
<!-- prettier-ignore-end -->

This state hook is a helper hook that will return `true` if the browser has [reduced motion](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-motion) enabled, otherwise, `false`.

This hook is based on [`matchMedia`](https://developer.mozilla.org/en-US/docs/Web/API/Window/matchMedia) and provides a React hook friendly wrapper for listening to state change.

If it is possible to slowdown or pause animation using CSS, always use the [CSS media feature `(prefers-reduced-motion: reduce)`](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-motion) instead. This hook is the last resort when CSS cannot be used to stop animation, such as SMIL animation.

## `useShouldSpeakIncomingActivity`

<!-- prettier-ignore-start -->
Expand Down
2 changes: 2 additions & 0 deletions packages/component/src/Assets/TypingAnimation.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import React from 'react';

import ScreenReaderText from '../ScreenReaderText';
import { useStyleToEmotionObject } from '../hooks/internal/styleToEmotionObject';
import testIds from '../testIds';
import useStyleSet from '../hooks/useStyleSet';

const { useDirection, useLocalizer } = hooks;
Expand Down Expand Up @@ -31,6 +32,7 @@ const TypingAnimation = () => {
rootClassName,
typingAnimationStyleSet + ''
)}
data-testid={testIds.typingIndicator}
/>
</React.Fragment>
);
Expand Down
Loading
Loading