Skip to content

Commit 2816bd8

Browse files
authored
Fix send box should narrate aria-label (#5805)
* Fix `aria-label` * Update PR number * Add aria-label * Add tests * Add test for Fluent
1 parent 84a05f7 commit 2816bd8

6 files changed

Lines changed: 325 additions & 95 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -421,6 +421,7 @@ Breaking changes in this release:
421421
- Fixed virtual keyboard should be collapsed after being suppressed, in iOS 26.3, by [@compulim](https://github.com/compulim) in PR [#5757](https://github.com/microsoft/BotFramework-WebChat/pull/5757)
422422
- Fixed Fluent/Copilot typing indicator animation background color, in PR [#5770](https://github.com/microsoft/BotFramework-WebChat/pull/5770), by [@OEvgeny](https://github.com/OEvgeny)
423423
- Fixed `<AddFullBundle>` should not re-render when `attachment[ForScreenReader]Middleware` is updated without noticeable different (`iterateEquals`), by [@compulim](https://github.com/compulim), in PR [#5779](https://github.com/microsoft/BotFramework-WebChat/pull/5779)
424+
- Fixed send box should narrate `aria-label` prop, by [@compulim](https://github.com/compulim), in PR [#5805](https://github.com/microsoft/BotFramework-WebChat/pull/5805)
424425

425426
## [4.18.0] - 2024-07-10
426427

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
<!doctype html>
2+
<html lang="en-US">
3+
<head>
4+
<link href="/assets/index.css" rel="stylesheet" type="text/css" />
5+
<style type="text/css">
6+
/* TODO: [P*] Can we eliminate this style? */
7+
.fui-FluentProvider,
8+
.webchat-fluent {
9+
height: 100%;
10+
}
11+
</style>
12+
</head>
13+
<body>
14+
<main id="webchat"></main>
15+
<!-- Redirect packages on esm.sh loaded by `@fluentui/react-components` -->
16+
<script type="importmap">
17+
{
18+
"imports": {
19+
"@fluentui/react-components": "https://esm.sh/@fluentui/react-components?deps=react@18.3.1,react-dom@18.3.1&exports=FluentProvider,createDarkTheme,webLightTheme",
20+
"botframework-webchat": "/__dist__/packages/bundle/static/botframework-webchat.js",
21+
"botframework-webchat/component": "/__dist__/packages/bundle/static/botframework-webchat/component.js",
22+
"botframework-webchat/decorator": "/__dist__/packages/bundle/static/botframework-webchat/decorator.js",
23+
"botframework-webchat/hook": "/__dist__/packages/bundle/static/botframework-webchat/hook.js",
24+
"botframework-webchat/internal": "/__dist__/packages/bundle/static/botframework-webchat/internal.js",
25+
"botframework-webchat-fluent-theme": "/__dist__/packages/fluent-theme/static/botframework-webchat-fluent-theme.js",
26+
"react": "/__dist__/packages/bundle/static/react.js",
27+
"react-dom": "/__dist__/packages/bundle/static/react-dom.js",
28+
"react-dom/client": "/__dist__/packages/bundle/static/react-dom/client.js",
29+
"https://esm.sh/react@18.3.1/es2022/react.mjs": "/__dist__/packages/bundle/static/react.js",
30+
"https://esm.sh/react@18.3.1/es2022/react-dom.mjs": "/__dist__/packages/bundle/static/react-dom.18.js",
31+
"https://esm.sh/react@18.3.1/es2022/react-dom/client.mjs": "/__dist__/packages/bundle/static/react-dom.18/client.js"
32+
}
33+
}
34+
</script>
35+
<script type="module">
36+
import '/test-harness.mjs';
37+
import '/test-page-object.mjs';
38+
39+
import { FluentProvider, webLightTheme } from '@fluentui/react-components';
40+
import { createDirectLine, createStoreWithOptions, hooks, ReactWebChat } from 'botframework-webchat';
41+
import { FluentThemeProvider } from 'botframework-webchat-fluent-theme';
42+
import { createElement } from 'react';
43+
import { createRoot } from 'react-dom/client';
44+
45+
const { useStyleOptions } = hooks;
46+
const {
47+
testHelpers: { createDirectLineEmulator }
48+
} = window;
49+
50+
// TODO: This is for `createDirectLineEmulator` only, should find ways to eliminate this line.
51+
window.WebChat = { createStoreWithOptions };
52+
53+
testHelpers.hideKnownError();
54+
55+
run(async function () {
56+
const { directLine, store } = createDirectLineEmulator();
57+
58+
const fluentTheme = {
59+
...webLightTheme,
60+
// Original is #242424 which is too light for fui-FluentProvider to pass our accessibility checks.
61+
colorNeutralForeground1: '#1b1b1b'
62+
};
63+
64+
createRoot(document.getElementsByTagName('main')[0]).render(
65+
createElement(
66+
FluentProvider,
67+
{ className: 'fui-FluentProvider', theme: fluentTheme },
68+
createElement(
69+
FluentThemeProvider,
70+
{ variant: 'fluent' },
71+
createElement(ReactWebChat, {
72+
directLine,
73+
overrideLocalizedStrings: {
74+
TEXT_INPUT_ALT: 'Hello, World!'
75+
},
76+
store
77+
})
78+
)
79+
)
80+
);
81+
82+
await pageConditions.uiConnected();
83+
84+
const sendBoxTextBox = document.querySelector('[data-testid="send box text area"]');
85+
86+
expect(sendBoxTextBox.tagName).toBe('TEXTAREA');
87+
expect(sendBoxTextBox.getAttribute('aria-label')).toBe('Hello, World!');
88+
});
89+
</script>
90+
</body>
91+
</html>
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<!doctype html>
2+
<html lang="en-US">
3+
<head>
4+
<link href="/assets/index.css" rel="stylesheet" type="text/css" />
5+
</head>
6+
<body>
7+
<main id="webchat"></main>
8+
<script type="importmap">
9+
{
10+
"imports": {
11+
"botframework-webchat": "/__dist__/packages/bundle/static/botframework-webchat.js",
12+
"react": "/__dist__/packages/bundle/static/react.js",
13+
"react-dom": "/__dist__/packages/bundle/static/react-dom.js"
14+
}
15+
}
16+
</script>
17+
<script type="module">
18+
import '/test-harness.mjs';
19+
import '/test-page-object.mjs';
20+
21+
import { createStoreWithOptions, renderWebChat, testIds } from 'botframework-webchat';
22+
23+
const {
24+
testHelpers: { createDirectLineEmulator }
25+
} = window;
26+
27+
// TODO: Should find ways to eliminate this line.
28+
window.WebChat = { createStoreWithOptions, testIds };
29+
30+
testHelpers.hideKnownError();
31+
32+
run(async function () {
33+
const { directLine, store } = createDirectLineEmulator();
34+
35+
renderWebChat(
36+
{
37+
directLine,
38+
overrideLocalizedStrings: {
39+
TEXT_INPUT_ALT: 'Hello, World!'
40+
},
41+
store
42+
},
43+
document.getElementById('webchat')
44+
);
45+
46+
await pageConditions.uiConnected();
47+
48+
expect(pageElements.sendBoxTextBox().tagName).toBe('INPUT');
49+
expect(pageElements.sendBoxTextBox().getAttribute('aria-label')).toBe('Hello, World!');
50+
});
51+
</script>
52+
</body>
53+
</html>
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
<!doctype html>
2+
<html lang="en-US">
3+
<head>
4+
<link href="/assets/index.css" rel="stylesheet" type="text/css" />
5+
</head>
6+
<body>
7+
<main id="webchat"></main>
8+
<script type="importmap">
9+
{
10+
"imports": {
11+
"botframework-webchat": "/__dist__/packages/bundle/static/botframework-webchat.js",
12+
"react": "/__dist__/packages/bundle/static/react.js",
13+
"react-dom": "/__dist__/packages/bundle/static/react-dom.js"
14+
}
15+
}
16+
</script>
17+
<script type="module">
18+
import '/test-harness.mjs';
19+
import '/test-page-object.mjs';
20+
21+
import { createStoreWithOptions, renderWebChat, testIds } from 'botframework-webchat';
22+
23+
const {
24+
testHelpers: { createDirectLineEmulator }
25+
} = window;
26+
27+
// TODO: Should find ways to eliminate this line.
28+
window.WebChat = { createStoreWithOptions, testIds };
29+
30+
testHelpers.hideKnownError();
31+
32+
run(async function () {
33+
const { directLine, store } = createDirectLineEmulator();
34+
35+
renderWebChat(
36+
{
37+
directLine,
38+
overrideLocalizedStrings: {
39+
TEXT_INPUT_ALT: 'Hello, World!'
40+
},
41+
store,
42+
styleOptions: {
43+
sendBoxTextWrap: true
44+
}
45+
},
46+
document.getElementById('webchat')
47+
);
48+
49+
await pageConditions.uiConnected();
50+
51+
expect(pageElements.sendBoxTextBox().tagName).toBe('TEXTAREA');
52+
expect(pageElements.sendBoxTextBox().getAttribute('aria-label')).toBe('Hello, World!');
53+
});
54+
</script>
55+
</body>
56+
</html>

packages/component/src/TextArea/TextArea.tsx

Lines changed: 31 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { hooks } from 'botframework-webchat-api';
21
import { useStyles } from '@msinternal/botframework-webchat-styles/react';
2+
import { hooks } from 'botframework-webchat-api';
33
import cx from 'classnames';
44
import React, {
55
forwardRef,
@@ -8,23 +8,23 @@ import React, {
88
useRef,
99
type FormEventHandler,
1010
type KeyboardEventHandler,
11-
type MouseEventHandler,
12-
type ReactNode
11+
type MouseEventHandler
1312
} from 'react';
13+
import { boolean, custom, number, object, optional, pipe, readonly, string, type InferInput } from 'valibot';
1414

15+
import { reactNode, validateProps } from '@msinternal/botframework-webchat-react-valibot';
1516
import styles from './TextArea.module.css';
1617

1718
const { useUIState } = hooks;
1819

19-
const TextArea = forwardRef<
20-
HTMLTextAreaElement,
21-
Readonly<{
22-
'aria-describedby'?: string | undefined;
23-
'aria-labelledby'?: string | undefined;
24-
className?: string | undefined;
25-
completion?: ReactNode | undefined;
26-
'data-testid'?: string | undefined;
27-
20+
const TextAreaPropsSchema = pipe(
21+
object({
22+
'aria-describedby': optional(string()),
23+
'aria-label': optional(string()),
24+
'aria-labelledby': optional(string()),
25+
className: optional(string()),
26+
completion: optional(reactNode()),
27+
'data-testid': optional(string()),
2828
/**
2929
* `true`, if the text area should be hidden but stay in the DOM, otherwise, `false`.
3030
*
@@ -33,15 +33,22 @@ const TextArea = forwardRef<
3333
* - When the DTMF keypad is going away, we need to send focus to the text area before we unmount DTMF keypad,
3434
* This ensures the flow of focus did not sent to document body
3535
*/
36-
hidden?: boolean | undefined;
37-
onClick?: MouseEventHandler<HTMLTextAreaElement> | undefined;
38-
onInput?: FormEventHandler<HTMLTextAreaElement> | undefined;
39-
placeholder?: string | undefined;
40-
readOnly?: boolean | undefined;
41-
startRows?: number | undefined;
42-
value?: string | undefined;
43-
}>
44-
>((props, ref) => {
36+
hidden: optional(boolean()),
37+
onClick: optional(custom<MouseEventHandler<HTMLTextAreaElement>>(value => typeof value === 'function')),
38+
onInput: optional(custom<FormEventHandler<HTMLTextAreaElement>>(value => typeof value === 'function')),
39+
placeholder: optional(string()),
40+
readOnly: optional(boolean()),
41+
startRows: optional(number()),
42+
value: optional(string())
43+
}),
44+
readonly()
45+
);
46+
47+
type TextAreaProps = InferInput<typeof TextAreaPropsSchema>;
48+
49+
const TextArea = forwardRef<HTMLTextAreaElement, TextAreaProps>((rawProps, ref) => {
50+
const props = validateProps(TextAreaPropsSchema, rawProps);
51+
4552
const [uiState] = useUIState();
4653
const classNames = useStyles(styles);
4754
const isInCompositionRef = useRef<boolean>(false);
@@ -84,11 +91,12 @@ const TextArea = forwardRef<
8491
) : (
8592
<Fragment>
8693
<div className={cx(classNames['text-area-doppelganger'], classNames['text-area-shared'])}>
87-
{props.completion ? props.completion : props.value}{' '}
94+
{props.completion || props.value}{' '}
8895
</div>
8996
<textarea
9097
aria-describedby={props['aria-describedby']}
9198
aria-disabled={disabled}
99+
aria-label={props['aria-label']}
92100
aria-labelledby={props['aria-labelledby']}
93101
aria-placeholder={props.placeholder}
94102
className={cx(classNames['text-area-input'], classNames['text-area-shared'])}
@@ -115,3 +123,4 @@ const TextArea = forwardRef<
115123
TextArea.displayName = 'TextArea';
116124

117125
export default TextArea;
126+
export { TextAreaPropsSchema, type TextAreaProps };

0 commit comments

Comments
 (0)