Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 48 additions & 16 deletions src/components/ReportActionItem/TaskPreview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import Navigation from '@libs/Navigation/Navigation';
import Parser from '@libs/Parser';
import {getOriginalMessage} from '@libs/ReportActionsUtils';
import {isCanceledTaskReport, isOpenTaskReport, isReportManager} from '@libs/ReportUtils';
import shouldBreakAccessibilityGrouping from '@libs/shouldBreakAccessibilityGrouping';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
Expand Down Expand Up @@ -106,6 +107,7 @@ function TaskPreview({action, chatReportID, currentUserPersonalDetails, isHovere
const iconWrapperStyle = StyleUtils.getTaskPreviewIconWrapper(hasAssignee ? avatarSize : undefined);

const shouldShowGreenDotIndicator = isOpenTaskReport(taskContextReport, action) && isReportManager(taskContextReport);
const taskAccessibilityLabel = taskTitleWithoutImage ? `${translate('task.task')}: ${taskTitleWithoutImage}` : translate('task.task');
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Raw html is announced here.

Image

if (isDeletedParentAction) {
return <RenderHTML html={`<deleted-action>${translate('parentReportAction.deletedTask')}</deleted-action>`} />;
}
Expand All @@ -121,6 +123,7 @@ function TaskPreview({action, chatReportID, currentUserPersonalDetails, isHovere
return (
<View style={[styles.chatItemMessage, !hasAssignee && styles.mv1]}>
<PressableWithoutFeedback
accessible={shouldBreakAccessibilityGrouping() ? false : undefined}
onPress={() => Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(taskReportID, undefined, undefined, Navigation.getActiveRoute()))}
onPressIn={() => canUseTouchScreen() && ControlSelection.block()}
onPressOut={() => ControlSelection.unblock()}
Expand All @@ -135,7 +138,7 @@ function TaskPreview({action, chatReportID, currentUserPersonalDetails, isHovere
shouldUseHapticsOnLongPress
style={[styles.flexRow, styles.justifyContentBetween, style]}
role={CONST.ROLE.BUTTON}
accessibilityLabel={translate('task.task')}
accessibilityLabel={taskAccessibilityLabel}
sentryLabel={CONST.SENTRY_LABEL.TASK.PREVIEW_CARD}
>
<View style={[styles.flex1, styles.flexRow, styles.alignItemsStart, styles.mr2]}>
Expand All @@ -151,26 +154,55 @@ function TaskPreview({action, chatReportID, currentUserPersonalDetails, isHovere
completeTask(taskContextReport, parentReport?.hasOutstandingChildTask ?? false, hasOutstandingChildTask, parentReportAction, delegateEmail, taskReportID);
}
})}
accessibilityLabel={translate('task.task')}
accessibilityLabel={taskAccessibilityLabel}
sentryLabel={CONST.SENTRY_LABEL.TASK.PREVIEW_CHECKBOX}
/>
</View>
{hasAssignee && (
<UserDetailsTooltip accountID={taskAssigneeAccountID}>
<View>
<Avatar
containerStyles={[styles.mr2, isTaskCompleted ? styles.opacitySemiTransparent : undefined]}
source={avatar}
size={avatarSize}
avatarID={taskAssigneeAccountID}
type={CONST.ICON_TYPE_AVATAR}
/>
{shouldBreakAccessibilityGrouping() ? (
<PressableWithoutFeedback
onPress={() => Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(taskReportID, undefined, undefined, Navigation.getActiveRoute()))}
style={[styles.flex1, styles.flexRow, styles.alignItemsStart]}
Comment thread
Krishna2323 marked this conversation as resolved.
Outdated
role={CONST.ROLE.BUTTON}
accessibilityLabel={taskAccessibilityLabel}
sentryLabel={CONST.SENTRY_LABEL.TASK.PREVIEW_CARD}
>
{hasAssignee && (
<UserDetailsTooltip accountID={taskAssigneeAccountID}>
<View>
<Avatar
containerStyles={[styles.mr2, isTaskCompleted ? styles.opacitySemiTransparent : undefined]}
source={avatar}
size={avatarSize}
avatarID={taskAssigneeAccountID}
type={CONST.ICON_TYPE_AVATAR}
/>
</View>
</UserDetailsTooltip>
)}
<View style={[styles.alignSelfCenter, styles.flex1]}>
<RenderHTML html={getTaskHTML()} />
</View>
</UserDetailsTooltip>
</PressableWithoutFeedback>
) : (
<>
{hasAssignee && (
<UserDetailsTooltip accountID={taskAssigneeAccountID}>
<View>
<Avatar
containerStyles={[styles.mr2, isTaskCompleted ? styles.opacitySemiTransparent : undefined]}
source={avatar}
size={avatarSize}
avatarID={taskAssigneeAccountID}
type={CONST.ICON_TYPE_AVATAR}
/>
</View>
</UserDetailsTooltip>
)}
<View style={[styles.alignSelfCenter, styles.flex1]}>
<RenderHTML html={getTaskHTML()} />
</View>
</>
)}
<View style={[styles.alignSelfCenter, styles.flex1]}>
<RenderHTML html={getTaskHTML()} />
</View>
</View>
{shouldShowGreenDotIndicator && (
<View style={iconWrapperStyle}>
Expand Down
65 changes: 51 additions & 14 deletions src/components/ReportActionItem/TaskView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import MenuItem from '@components/MenuItem';
import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription';
import OfflineWithFeedback from '@components/OfflineWithFeedback';
import {usePersonalDetails} from '@components/OnyxListItemProvider';
import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback';
import PressableWithSecondaryInteraction from '@components/PressableWithSecondaryInteraction';
import RenderHTML from '@components/RenderHTML';
import {ShowContextMenuActionsContext, ShowContextMenuStateContext} from '@components/ShowContextMenuContext';
Expand All @@ -30,6 +31,7 @@ import Navigation from '@libs/Navigation/Navigation';
import {getPersonalDetailsForAccountIDs} from '@libs/OptionsListUtils';
import Parser from '@libs/Parser';
import {getDisplayNameForParticipant, getDisplayNamesWithTooltips, isCompletedTaskReport, isOpenTaskReport} from '@libs/ReportUtils';
import shouldBreakAccessibilityGrouping from '@libs/shouldBreakAccessibilityGrouping';
import StringUtils from '@libs/StringUtils';
import {isActiveTaskEditRoute} from '@libs/TaskUtils';
import {callFunctionIfActionIsAllowed} from '@userActions/Session';
Expand Down Expand Up @@ -70,6 +72,7 @@ function TaskView({report, parentReport, action}: TaskViewProps) {
const taskTitleWithoutPre = StringUtils.removePreCodeBlock(report?.reportName);
const titleWithoutImage = Parser.replace(Parser.htmlToMarkdown(taskTitleWithoutPre), {disabledRules: [...CONST.TASK_TITLE_DISABLED_RULES]});
const taskTitle = `<task-title>${titleWithoutImage}</task-title>`;
const taskAccessibilityLabel = titleWithoutImage ? `${translate('task.task')}: ${titleWithoutImage}` : translate('task.task');

const assigneeTooltipDetails = getDisplayNamesWithTooltips(
getPersonalDetailsForAccountIDs(report?.managerID ? [report?.managerID] : [], personalDetails),
Expand Down Expand Up @@ -136,6 +139,7 @@ function TaskView({report, parentReport, action}: TaskViewProps) {
<Hoverable>
{(hovered) => (
<PressableWithSecondaryInteraction
accessible={shouldBreakAccessibilityGrouping() ? false : undefined}
onPress={callFunctionIfActionIsAllowed((e) => {
if (isDisableInteractive) {
return;
Expand All @@ -152,7 +156,7 @@ function TaskView({report, parentReport, action}: TaskViewProps) {
StyleUtils.getButtonBackgroundColorStyle(getButtonState(hovered, pressed, false, disableState, !isDisableInteractive), true),
isDisableInteractive && styles.cursorDefault,
]}
accessibilityLabel={taskTitle || translate('task.task')}
accessibilityLabel={taskAccessibilityLabel}
disabled={isDisableInteractive}
sentryLabel={CONST.SENTRY_LABEL.TASK.VIEW_TITLE}
>
Expand All @@ -162,7 +166,6 @@ function TaskView({report, parentReport, action}: TaskViewProps) {
<View style={[styles.flexRow, styles.flex1]}>
<Checkbox
onPress={callFunctionIfActionIsAllowed(() => {
// If we're already navigating to these task editing pages, early return not to mark as completed, otherwise we would have not found page.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please restore this comment

if (isActiveTaskEditRoute(report?.reportID)) {
return;
}
Expand All @@ -177,21 +180,55 @@ function TaskView({report, parentReport, action}: TaskViewProps) {
containerSize={24}
containerBorderRadius={8}
caretSize={16}
accessibilityLabel={taskTitle || translate('task.task')}
accessibilityLabel={taskAccessibilityLabel}
disabled={!isTaskActionable}
sentryLabel={CONST.SENTRY_LABEL.TASK.VIEW_CHECKBOX}
/>
<View style={[styles.flexRow, styles.flex1]}>
<RenderHTML html={taskTitle} />
</View>
{!isDisableInteractive && (
<View style={styles.taskRightIconContainer}>
<Icon
additionalStyles={[styles.alignItemsCenter]}
src={icons.ArrowRight}
fill={StyleUtils.getIconFillColor(getButtonState(hovered, pressed, false, disableState))}
/>
</View>
{shouldBreakAccessibilityGrouping() ? (
<PressableWithoutFeedback
onPress={callFunctionIfActionIsAllowed((e) => {
if (isDisableInteractive) {
return;
}
if (e?.type === 'click') {
(e.currentTarget as HTMLElement).blur();
}
Comment thread
Krishna2323 marked this conversation as resolved.
Outdated
Navigation.navigate(createDynamicRoute(DYNAMIC_ROUTES.TASK_TITLE.path));
})}
role={CONST.ROLE.BUTTON}
accessibilityLabel={taskAccessibilityLabel}
disabled={isDisableInteractive}
style={[styles.flexRow, styles.flex1]}
sentryLabel={CONST.SENTRY_LABEL.TASK.VIEW_TITLE}
>
<View style={[styles.flexRow, styles.flex1]}>
<RenderHTML html={taskTitle} />
</View>
{!isDisableInteractive && (
<View style={styles.taskRightIconContainer}>
<Icon
additionalStyles={[styles.alignItemsCenter]}
src={icons.ArrowRight}
fill={StyleUtils.getIconFillColor(getButtonState(hovered, pressed, false, disableState))}
/>
</View>
)}
</PressableWithoutFeedback>
) : (
<>
<View style={[styles.flexRow, styles.flex1]}>
<RenderHTML html={taskTitle} />
</View>
{!isDisableInteractive && (
<View style={styles.taskRightIconContainer}>
<Icon
additionalStyles={[styles.alignItemsCenter]}
src={icons.ArrowRight}
fill={StyleUtils.getIconFillColor(getButtonState(hovered, pressed, false, disableState))}
/>
</View>
)}
</>
)}
</View>
</OfflineWithFeedback>
Expand Down
8 changes: 8 additions & 0 deletions src/libs/shouldBreakAccessibilityGrouping/index.ios.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/**
* On iOS, VoiceOver groups all children of an accessible parent into a single
* focus target, preventing individual elements from being focusable. Returning
* true signals that the parent should set accessible={false} to break this grouping.
*/
export default function shouldBreakAccessibilityGrouping(): boolean {
return true;
}
7 changes: 7 additions & 0 deletions src/libs/shouldBreakAccessibilityGrouping/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/**
* On non-iOS platforms, accessible elements don't group children in a way
* that prevents individual focus, so we don't need to break grouping.
*/
export default function shouldBreakAccessibilityGrouping(): boolean {
return false;
}
3 changes: 3 additions & 0 deletions src/pages/inbox/report/PureReportActionItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import {
getReportActionMessage,
getReportActionText,
getWhisperedTo,
isCreatedTaskReportAction,
isDeletedParentAction as isDeletedParentActionUtils,
isMessageDeleted,
isMoneyRequestAction,
Expand All @@ -62,6 +63,7 @@ import {
shouldDisplayThreadReplies as shouldDisplayThreadRepliesUtils,
} from '@libs/ReportUtils';
import SelectionScraper from '@libs/SelectionScraper';
import shouldBreakAccessibilityGrouping from '@libs/shouldBreakAccessibilityGrouping';
import {ReactionListContext} from '@pages/inbox/ReportScreenContext';
import AttachmentModalContext from '@pages/media/AttachmentModalScreen/AttachmentModalContext';
import {clearAllRelatedReportActionErrors} from '@userActions/ClearReportActionErrors';
Expand Down Expand Up @@ -522,6 +524,7 @@ function PureReportActionItem({
)}
<PressableWithSecondaryInteraction
ref={popoverAnchorRef}
accessible={shouldBreakAccessibilityGrouping() && isCreatedTaskReportAction(action) ? false : undefined}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As setting false here, no pressed feedback. I'm afraid QA will mark this as regression

Screen.Recording.2026-05-25.at.3.50.00.AM.mov

production:

Screen.Recording.2026-05-25.at.3.51.11.AM.mov

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tried to fix that, could you please check?

onPress={() => {
if (!hasDraft) {
onPress?.();
Expand Down
Loading