[MM-61610]: Added aria-activedescendant to the textbox (#29900)
* [MA-17]: Added aria-activedescendant to the textbox * [MA-17]: Fixed id and ARIA attribute * Rebased with master branch * [MA-17]: Removed irrelevant attribute * [MA-17]: Updated the logic to add textbox id to At mention suggestion --------- Co-authored-by: Mattermost Build <build@mattermost.com>
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
a7905541b8
Коммит
8b2e1483c9
@@ -110,6 +110,7 @@ exports[`components/TextBox should match snapshot with additional, optional prop
|
|||||||
"priorityProfiles": undefined,
|
"priorityProfiles": undefined,
|
||||||
"requestStarted": false,
|
"requestStarted": false,
|
||||||
"searchAssociatedGroupsForReference": [Function],
|
"searchAssociatedGroupsForReference": [Function],
|
||||||
|
"textboxId": "someid",
|
||||||
"triggerCharacter": "@",
|
"triggerCharacter": "@",
|
||||||
"useChannelMentions": true,
|
"useChannelMentions": true,
|
||||||
},
|
},
|
||||||
@@ -257,6 +258,7 @@ exports[`components/TextBox should match snapshot with required props 1`] = `
|
|||||||
"priorityProfiles": undefined,
|
"priorityProfiles": undefined,
|
||||||
"requestStarted": false,
|
"requestStarted": false,
|
||||||
"searchAssociatedGroupsForReference": [Function],
|
"searchAssociatedGroupsForReference": [Function],
|
||||||
|
"textboxId": "someid",
|
||||||
"triggerCharacter": "@",
|
"triggerCharacter": "@",
|
||||||
"useChannelMentions": true,
|
"useChannelMentions": true,
|
||||||
},
|
},
|
||||||
@@ -404,6 +406,7 @@ exports[`components/TextBox should throw error when new property is too long 1`]
|
|||||||
"priorityProfiles": undefined,
|
"priorityProfiles": undefined,
|
||||||
"requestStarted": false,
|
"requestStarted": false,
|
||||||
"searchAssociatedGroupsForReference": [Function],
|
"searchAssociatedGroupsForReference": [Function],
|
||||||
|
"textboxId": "someid",
|
||||||
"triggerCharacter": "@",
|
"triggerCharacter": "@",
|
||||||
"useChannelMentions": true,
|
"useChannelMentions": true,
|
||||||
},
|
},
|
||||||
@@ -551,6 +554,7 @@ exports[`components/TextBox should throw error when value is too long 1`] = `
|
|||||||
"priorityProfiles": undefined,
|
"priorityProfiles": undefined,
|
||||||
"requestStarted": false,
|
"requestStarted": false,
|
||||||
"searchAssociatedGroupsForReference": [Function],
|
"searchAssociatedGroupsForReference": [Function],
|
||||||
|
"textboxId": "someid",
|
||||||
"triggerCharacter": "@",
|
"triggerCharacter": "@",
|
||||||
"useChannelMentions": true,
|
"useChannelMentions": true,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ exports[`at mention suggestion Should display nick name of non signed in user 1`
|
|||||||
"id": "userid2",
|
"id": "userid2",
|
||||||
"last_name": "b",
|
"last_name": "b",
|
||||||
"nickname": "c",
|
"nickname": "c",
|
||||||
|
"textboxId": "post_textbox",
|
||||||
"username": "user2",
|
"username": "user2",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -19,6 +20,7 @@ exports[`at mention suggestion Should display nick name of non signed in user 1`
|
|||||||
>
|
>
|
||||||
<SuggestionContainer
|
<SuggestionContainer
|
||||||
data-testid="mentionSuggestion_user2"
|
data-testid="mentionSuggestion_user2"
|
||||||
|
id="post_textbox_@user2"
|
||||||
isSelection={false}
|
isSelection={false}
|
||||||
item={
|
item={
|
||||||
Object {
|
Object {
|
||||||
@@ -26,20 +28,23 @@ exports[`at mention suggestion Should display nick name of non signed in user 1`
|
|||||||
"id": "userid2",
|
"id": "userid2",
|
||||||
"last_name": "b",
|
"last_name": "b",
|
||||||
"nickname": "c",
|
"nickname": "c",
|
||||||
|
"textboxId": "post_textbox",
|
||||||
"username": "user2",
|
"username": "user2",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
matchedPretext="@"
|
matchedPretext="@"
|
||||||
onClick={[MockFunction]}
|
onClick={[MockFunction]}
|
||||||
onMouseMove={[MockFunction]}
|
onMouseMove={[MockFunction]}
|
||||||
|
role="option"
|
||||||
term="@user"
|
term="@user"
|
||||||
>
|
>
|
||||||
<li
|
<li
|
||||||
className="suggestion-list__item"
|
className="suggestion-list__item"
|
||||||
data-testid="mentionSuggestion_user2"
|
data-testid="mentionSuggestion_user2"
|
||||||
|
id="post_textbox_@user2"
|
||||||
onClick={[Function]}
|
onClick={[Function]}
|
||||||
onMouseMove={[Function]}
|
onMouseMove={[Function]}
|
||||||
role="button"
|
role="option"
|
||||||
tabIndex={-1}
|
tabIndex={-1}
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
@@ -103,6 +108,7 @@ exports[`at mention suggestion Should not display nick name of the signed in use
|
|||||||
"isCurrentUser": true,
|
"isCurrentUser": true,
|
||||||
"last_name": "b",
|
"last_name": "b",
|
||||||
"nickname": "c",
|
"nickname": "c",
|
||||||
|
"textboxId": "post_textbox",
|
||||||
"username": "user",
|
"username": "user",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -113,6 +119,7 @@ exports[`at mention suggestion Should not display nick name of the signed in use
|
|||||||
>
|
>
|
||||||
<SuggestionContainer
|
<SuggestionContainer
|
||||||
data-testid="mentionSuggestion_user"
|
data-testid="mentionSuggestion_user"
|
||||||
|
id="post_textbox_@user"
|
||||||
isSelection={false}
|
isSelection={false}
|
||||||
item={
|
item={
|
||||||
Object {
|
Object {
|
||||||
@@ -121,20 +128,23 @@ exports[`at mention suggestion Should not display nick name of the signed in use
|
|||||||
"isCurrentUser": true,
|
"isCurrentUser": true,
|
||||||
"last_name": "b",
|
"last_name": "b",
|
||||||
"nickname": "c",
|
"nickname": "c",
|
||||||
|
"textboxId": "post_textbox",
|
||||||
"username": "user",
|
"username": "user",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
matchedPretext="@"
|
matchedPretext="@"
|
||||||
onClick={[MockFunction]}
|
onClick={[MockFunction]}
|
||||||
onMouseMove={[MockFunction]}
|
onMouseMove={[MockFunction]}
|
||||||
|
role="option"
|
||||||
term="@user"
|
term="@user"
|
||||||
>
|
>
|
||||||
<li
|
<li
|
||||||
className="suggestion-list__item"
|
className="suggestion-list__item"
|
||||||
data-testid="mentionSuggestion_user"
|
data-testid="mentionSuggestion_user"
|
||||||
|
id="post_textbox_@user"
|
||||||
onClick={[Function]}
|
onClick={[Function]}
|
||||||
onMouseMove={[Function]}
|
onMouseMove={[Function]}
|
||||||
role="button"
|
role="option"
|
||||||
tabIndex={-1}
|
tabIndex={-1}
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ type Results = {
|
|||||||
type ResultsCallback = (results: Results) => void;
|
type ResultsCallback = (results: Results) => void;
|
||||||
|
|
||||||
export type Props = {
|
export type Props = {
|
||||||
|
textboxId?: string;
|
||||||
currentUserId: string;
|
currentUserId: string;
|
||||||
channelId: string;
|
channelId: string;
|
||||||
autocompleteUsersInChannel: (prefix: string) => Promise<ActionResult>;
|
autocompleteUsersInChannel: (prefix: string) => Promise<ActionResult>;
|
||||||
@@ -58,6 +59,7 @@ export type Props = {
|
|||||||
// users in the channel and users not in the channel. It mixes together results from the local
|
// users in the channel and users not in the channel. It mixes together results from the local
|
||||||
// store with results fetched from the server.
|
// store with results fetched from the server.
|
||||||
export default class AtMentionProvider extends Provider {
|
export default class AtMentionProvider extends Provider {
|
||||||
|
public textboxId?: string;
|
||||||
public currentUserId: string;
|
public currentUserId: string;
|
||||||
public channelId: string;
|
public channelId: string;
|
||||||
public autocompleteUsersInChannel: (prefix: string) => Promise<ActionResult>;
|
public autocompleteUsersInChannel: (prefix: string) => Promise<ActionResult>;
|
||||||
@@ -75,8 +77,9 @@ export default class AtMentionProvider extends Provider {
|
|||||||
constructor(props: Props) {
|
constructor(props: Props) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
const {currentUserId, channelId, autocompleteUsersInChannel, useChannelMentions, autocompleteGroups, searchAssociatedGroupsForReference, priorityProfiles} = props;
|
const {currentUserId, channelId, autocompleteUsersInChannel, useChannelMentions, autocompleteGroups, searchAssociatedGroupsForReference, priorityProfiles, textboxId} = props;
|
||||||
|
|
||||||
|
this.textboxId = textboxId;
|
||||||
this.currentUserId = currentUserId;
|
this.currentUserId = currentUserId;
|
||||||
this.channelId = channelId;
|
this.channelId = channelId;
|
||||||
this.autocompleteUsersInChannel = autocompleteUsersInChannel;
|
this.autocompleteUsersInChannel = autocompleteUsersInChannel;
|
||||||
@@ -93,7 +96,8 @@ export default class AtMentionProvider extends Provider {
|
|||||||
this.addLastViewAtToProfiles = makeAddLastViewAtToProfiles();
|
this.addLastViewAtToProfiles = makeAddLastViewAtToProfiles();
|
||||||
}
|
}
|
||||||
|
|
||||||
setProps({currentUserId, channelId, autocompleteUsersInChannel, useChannelMentions, autocompleteGroups, searchAssociatedGroupsForReference, priorityProfiles}: Props) {
|
setProps({currentUserId, channelId, autocompleteUsersInChannel, useChannelMentions, autocompleteGroups, searchAssociatedGroupsForReference, priorityProfiles, textboxId}: Props) {
|
||||||
|
this.textboxId = textboxId;
|
||||||
this.currentUserId = currentUserId;
|
this.currentUserId = currentUserId;
|
||||||
this.channelId = channelId;
|
this.channelId = channelId;
|
||||||
this.autocompleteUsersInChannel = autocompleteUsersInChannel;
|
this.autocompleteUsersInChannel = autocompleteUsersInChannel;
|
||||||
@@ -362,19 +366,24 @@ export default class AtMentionProvider extends Provider {
|
|||||||
} else if (this.lastPrefixWithNoResults === this.latestPrefix) {
|
} else if (this.lastPrefixWithNoResults === this.latestPrefix) {
|
||||||
this.lastPrefixWithNoResults = '';
|
this.lastPrefixWithNoResults = '';
|
||||||
}
|
}
|
||||||
const mentions = items.map((item) => {
|
const mentions: string[] = [];
|
||||||
|
|
||||||
|
// Add the textboxId for each suggestions
|
||||||
|
const modifiedItems = items.map((item) => {
|
||||||
if (item.username) {
|
if (item.username) {
|
||||||
return '@' + item.username;
|
mentions.push('@' + item.username);
|
||||||
} else if (item.name) {
|
} else if (item.name) {
|
||||||
return '@' + item.name;
|
mentions.push('@' + item.name);
|
||||||
|
} else {
|
||||||
|
mentions.push('');
|
||||||
}
|
}
|
||||||
return '';
|
return {...item, textboxId: this.textboxId};
|
||||||
});
|
});
|
||||||
|
|
||||||
resultCallback({
|
resultCallback({
|
||||||
matchedPretext: `@${this.latestPrefix}`,
|
matchedPretext: `@${this.latestPrefix}`,
|
||||||
terms: mentions,
|
terms: mentions,
|
||||||
items,
|
items: modifiedItems,
|
||||||
component: AtMentionSuggestion,
|
component: AtMentionSuggestion,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ describe('at mention suggestion', () => {
|
|||||||
last_name: 'b',
|
last_name: 'b',
|
||||||
nickname: 'c',
|
nickname: 'c',
|
||||||
isCurrentUser: true,
|
isCurrentUser: true,
|
||||||
|
textboxId: 'post_textbox',
|
||||||
} as Item;
|
} as Item;
|
||||||
|
|
||||||
const userid2 = {
|
const userid2 = {
|
||||||
@@ -28,6 +29,7 @@ describe('at mention suggestion', () => {
|
|||||||
first_name: 'a',
|
first_name: 'a',
|
||||||
last_name: 'b',
|
last_name: 'b',
|
||||||
nickname: 'c',
|
nickname: 'c',
|
||||||
|
textboxId: 'post_textbox',
|
||||||
} as Item;
|
} as Item;
|
||||||
|
|
||||||
const baseProps = {
|
const baseProps = {
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ export interface Item extends UserProfile {
|
|||||||
name: string;
|
name: string;
|
||||||
isCurrentUser: boolean;
|
isCurrentUser: boolean;
|
||||||
type: string;
|
type: string;
|
||||||
|
textboxId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Group extends Item {
|
interface Group extends Item {
|
||||||
@@ -52,7 +53,10 @@ const AtMentionSuggestion = React.forwardRef<HTMLLIElement, SuggestionProps<Item
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
icon = (
|
icon = (
|
||||||
<span className='suggestion-list__icon suggestion-list__icon--large'>
|
<span
|
||||||
|
className='suggestion-list__icon suggestion-list__icon--large'
|
||||||
|
aria-hidden='true'
|
||||||
|
>
|
||||||
<i
|
<i
|
||||||
className='icon icon-account-multiple-outline'
|
className='icon icon-account-multiple-outline'
|
||||||
title={intl.formatMessage({id: 'generic_icons.member', defaultMessage: 'Member Icon'})}
|
title={intl.formatMessage({id: 'generic_icons.member', defaultMessage: 'Member Icon'})}
|
||||||
@@ -68,7 +72,10 @@ const AtMentionSuggestion = React.forwardRef<HTMLLIElement, SuggestionProps<Item
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
icon = (
|
icon = (
|
||||||
<span className='suggestion-list__icon suggestion-list__icon--large'>
|
<span
|
||||||
|
className='suggestion-list__icon suggestion-list__icon--large'
|
||||||
|
aria-hidden='true'
|
||||||
|
>
|
||||||
<i
|
<i
|
||||||
className='icon icon-account-multiple-outline'
|
className='icon icon-account-multiple-outline'
|
||||||
title={intl.formatMessage({id: 'generic_icons.member', defaultMessage: 'Member Icon'})}
|
title={intl.formatMessage({id: 'generic_icons.member', defaultMessage: 'Member Icon'})}
|
||||||
@@ -84,7 +91,10 @@ const AtMentionSuggestion = React.forwardRef<HTMLLIElement, SuggestionProps<Item
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
icon = (
|
icon = (
|
||||||
<span className='suggestion-list__icon suggestion-list__icon--large'>
|
<span
|
||||||
|
className='suggestion-list__icon suggestion-list__icon--large'
|
||||||
|
aria-hidden='true'
|
||||||
|
>
|
||||||
<i
|
<i
|
||||||
className='icon icon-account-multiple-outline'
|
className='icon icon-account-multiple-outline'
|
||||||
title={intl.formatMessage({id: 'generic_icons.member', defaultMessage: 'Member Icon'})}
|
title={intl.formatMessage({id: 'generic_icons.member', defaultMessage: 'Member Icon'})}
|
||||||
@@ -97,7 +107,10 @@ const AtMentionSuggestion = React.forwardRef<HTMLLIElement, SuggestionProps<Item
|
|||||||
<span className='ml-1'>{'- '}{item.display_name}</span>
|
<span className='ml-1'>{'- '}{item.display_name}</span>
|
||||||
);
|
);
|
||||||
icon = (
|
icon = (
|
||||||
<span className='suggestion-list__icon suggestion-list__icon--large'>
|
<span
|
||||||
|
className='suggestion-list__icon suggestion-list__icon--large'
|
||||||
|
aria-hidden='true'
|
||||||
|
>
|
||||||
<i
|
<i
|
||||||
className='icon icon-account-multiple-outline'
|
className='icon icon-account-multiple-outline'
|
||||||
title={intl.formatMessage({id: 'generic_icons.member', defaultMessage: 'Member Icon'})}
|
title={intl.formatMessage({id: 'generic_icons.member', defaultMessage: 'Member Icon'})}
|
||||||
@@ -175,8 +188,10 @@ const AtMentionSuggestion = React.forwardRef<HTMLLIElement, SuggestionProps<Item
|
|||||||
return (
|
return (
|
||||||
<SuggestionContainer
|
<SuggestionContainer
|
||||||
ref={ref}
|
ref={ref}
|
||||||
|
role='option'
|
||||||
{...props}
|
{...props}
|
||||||
data-testid={`mentionSuggestion_${itemname}`}
|
data-testid={`mentionSuggestion_${itemname}`}
|
||||||
|
id={`${item.textboxId}_@${itemname}`}
|
||||||
>
|
>
|
||||||
{icon}
|
{icon}
|
||||||
<span className='suggestion-list__ellipsis'>
|
<span className='suggestion-list__ellipsis'>
|
||||||
|
|||||||
@@ -206,7 +206,7 @@ export default class SuggestionList extends React.PureComponent<Props> {
|
|||||||
renderDivider(type: string) {
|
renderDivider(type: string) {
|
||||||
const id = type ? 'suggestion.' + type : 'suggestion.default';
|
const id = type ? 'suggestion.' + type : 'suggestion.default';
|
||||||
return (
|
return (
|
||||||
<div
|
<li
|
||||||
key={type + '-divider'}
|
key={type + '-divider'}
|
||||||
className='suggestion-list__divider'
|
className='suggestion-list__divider'
|
||||||
role='separator'
|
role='separator'
|
||||||
@@ -214,7 +214,7 @@ export default class SuggestionList extends React.PureComponent<Props> {
|
|||||||
<span>
|
<span>
|
||||||
<FormattedMessage id={id}/>
|
<FormattedMessage id={id}/>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</li>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -330,8 +330,6 @@ function mapStateToPropsForSwitchChannelSuggestion(state: GlobalState, ownProps:
|
|||||||
collapsedThreads,
|
collapsedThreads,
|
||||||
team,
|
team,
|
||||||
isPartOfOnlyOneTeam,
|
isPartOfOnlyOneTeam,
|
||||||
|
|
||||||
// id: 'quickSwitchInput',
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -105,6 +105,7 @@ export default class Textbox extends React.PureComponent<Props> {
|
|||||||
|
|
||||||
this.suggestionProviders.push(
|
this.suggestionProviders.push(
|
||||||
new AtMentionProvider({
|
new AtMentionProvider({
|
||||||
|
textboxId: this.props.id,
|
||||||
currentUserId: this.props.currentUserId,
|
currentUserId: this.props.currentUserId,
|
||||||
channelId: this.props.channelId,
|
channelId: this.props.channelId,
|
||||||
autocompleteUsersInChannel: (prefix: string) => this.props.actions.autocompleteUsersInChannel(prefix, this.props.channelId),
|
autocompleteUsersInChannel: (prefix: string) => this.props.actions.autocompleteUsersInChannel(prefix, this.props.channelId),
|
||||||
@@ -146,6 +147,7 @@ export default class Textbox extends React.PureComponent<Props> {
|
|||||||
for (const provider of this.suggestionProviders) {
|
for (const provider of this.suggestionProviders) {
|
||||||
if (provider instanceof AtMentionProvider) {
|
if (provider instanceof AtMentionProvider) {
|
||||||
provider.setProps({
|
provider.setProps({
|
||||||
|
textboxId: this.props.id,
|
||||||
currentUserId: this.props.currentUserId,
|
currentUserId: this.props.currentUserId,
|
||||||
channelId: this.props.channelId,
|
channelId: this.props.channelId,
|
||||||
autocompleteUsersInChannel: (prefix: string) => this.props.actions.autocompleteUsersInChannel(prefix, this.props.channelId),
|
autocompleteUsersInChannel: (prefix: string) => this.props.actions.autocompleteUsersInChannel(prefix, this.props.channelId),
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user