Skip to content

Commit

Permalink
feat(ActionSheet, Alert): add stopPropagation ignore (#8166)
Browse files Browse the repository at this point in the history
* fix(ActionSheet): add stopPropagation ignore

* fix review notes
  • Loading branch information
BlackySoul authored Jan 27, 2025
1 parent 42260bd commit 363b5cd
Show file tree
Hide file tree
Showing 7 changed files with 108 additions and 8 deletions.
36 changes: 36 additions & 0 deletions packages/vkui/src/components/ActionSheet/ActionSheet.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -261,4 +261,40 @@ describe(ActionSheet, () => {
// desktop Android
expect(screen.queryByText('Отмена')).toBeFalsy();
});

describe('handle allowClickPropagation correctly', () => {
it.each([
['menu', ActionSheetMenu],
['sheet', ActionSheetSheet],
])('%s', async (_name, ActionSheet) => {
const onClose = jest.fn();
const onClick = jest.fn();
const { rerender } = render(
<div onClick={onClick}>
<ActionSheet data-testid="container" onClose={onClose}>
<div data-testid="content" />
</ActionSheet>
</div>,
);
await waitForFloatingPosition();
act(jest.runAllTimers);

await userEvent.click(screen.getByTestId('content'));
expect(onClick).not.toHaveBeenCalled();

rerender(
<div onClick={onClick}>
<ActionSheet data-testid="container" onClose={onClose} allowClickPropagation>
<div data-testid="content" />
</ActionSheet>
</div>,
);

await waitForFloatingPosition();
act(jest.runAllTimers);

await userEvent.click(screen.getByTestId('content'));
expect(onClick).toHaveBeenCalled();
});
});
});
5 changes: 4 additions & 1 deletion packages/vkui/src/components/ActionSheet/ActionSheet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,10 @@ export interface ActionSheetOnCloseOptions {
}

export interface ActionSheetProps
extends Pick<SharedDropdownProps, 'toggleRef' | 'popupOffsetDistance' | 'placement'>,
extends Pick<
SharedDropdownProps,
'toggleRef' | 'popupOffsetDistance' | 'placement' | 'allowClickPropagation'
>,
Omit<UseFocusTrapProps, 'onClose'>,
Omit<React.HTMLAttributes<HTMLDivElement>, 'autoFocus' | 'title'> {
title?: React.ReactNode;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { useEventListener } from '../../hooks/useEventListener';
import { usePlatform } from '../../hooks/usePlatform';
import { useDOM } from '../../lib/dom';
import { isRefObject } from '../../lib/isRefObject';
import { stopPropagation } from '../../lib/utils';
import { warnOnce } from '../../lib/warnOnce';
import { FocusTrap } from '../FocusTrap/FocusTrap';
import { Popper } from '../Popper/Popper';
Expand All @@ -30,6 +31,8 @@ export const ActionSheetDropdownMenu = ({
placement,
onAnimationStart,
onAnimationEnd,
allowClickPropagation = false,
onClick,
...restProps
}: SharedDropdownProps): React.ReactNode => {
const { document } = useDOM();
Expand Down Expand Up @@ -57,8 +60,6 @@ export const ActionSheetDropdownMenu = ({
});
}, [bodyClickListener, document]);

const onClick = React.useCallback((e: React.MouseEvent<HTMLElement>) => e.stopPropagation(), []);

const targetRef = React.useMemo(() => {
if (isRefObject<SharedDropdownProps['toggleRef'], HTMLElement>(toggleRef)) {
return toggleRef;
Expand All @@ -67,6 +68,13 @@ export const ActionSheetDropdownMenu = ({
return { current: toggleRef as HTMLElement };
}, [toggleRef]);

const handleClick = allowClickPropagation
? onClick
: (event: React.MouseEvent<HTMLElement>) => {
stopPropagation(event);
onClick?.(event);
};

return (
<Popper
targetRef={targetRef}
Expand All @@ -86,7 +94,7 @@ export const ActionSheetDropdownMenu = ({
onAnimationStart={onAnimationStart}
onAnimationEnd={onAnimationEnd}
>
<FocusTrap onClose={onClose} {...restProps} onClick={onClick}>
<FocusTrap onClose={onClose} {...restProps} onClick={handleClick}>
{children}
</FocusTrap>
</Popper>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,11 @@ import * as React from 'react';
import { classNames } from '@vkontakte/vkjs';
import { useAdaptivityWithJSMediaQueries } from '../../hooks/useAdaptivityWithJSMediaQueries';
import { usePlatform } from '../../hooks/usePlatform';
import { stopPropagation } from '../../lib/utils';
import { FocusTrap } from '../FocusTrap/FocusTrap';
import type { SharedDropdownProps } from './types';
import styles from './ActionSheet.module.css';

const stopPropagation: React.MouseEventHandler = (e) => e.stopPropagation();

export type ActionSheetDropdownProps = Omit<
SharedDropdownProps,
'popupDirection' | 'popupOffsetDistance' | 'placement'
Expand All @@ -21,15 +20,24 @@ export const ActionSheetDropdownSheet = ({
// these 2 props are only omitted - ActionSheetDesktop compat
toggleRef,
className,
onClick,
allowClickPropagation = false,
...restProps
}: SharedDropdownProps): React.ReactNode => {
const { sizeY } = useAdaptivityWithJSMediaQueries();
const platform = usePlatform();

const handleClick = allowClickPropagation
? onClick
: (event: React.MouseEvent<HTMLElement>) => {
stopPropagation(event);
onClick?.(event);
};

return (
<FocusTrap
{...restProps}
onClick={stopPropagation}
onClick={handleClick}
className={classNames(
styles.host,
platform === 'ios' && styles.ios,
Expand Down
4 changes: 4 additions & 0 deletions packages/vkui/src/components/ActionSheet/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,8 @@ export interface SharedDropdownProps extends FocusTrapProps {
* Отступ, где заданное кол-во единиц равняется пикселям
* */
popupOffsetDistance?: number;
/**
* По умолчанию событие onClick не всплывает
*/
allowClickPropagation?: boolean;
}
28 changes: 28 additions & 0 deletions packages/vkui/src/components/Alert/Alert.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -333,4 +333,32 @@ describe('Alert', () => {
descriptionClassNames.forEach((className) => expect(textElement).toHaveClass(className));
},
);

it('handle allowClickPropagation correctly', async () => {
const onClose = jest.fn();
const onClick = jest.fn();
const action = {
'title': 'Item',
'data-testid': '__action__',
'autoCloseDisabled': true,
'mode': 'default' as const,
};
const result = render(
<div onClick={onClick}>
<Alert onClose={onClose} actions={[action]} />
</div>,
);

await userEvent.click(result.getByTestId('__action__'));
expect(onClick).not.toHaveBeenCalled();

result.rerender(
<div onClick={onClick}>
<Alert onClose={onClose} actions={[action]} allowClickPropagation />
</div>,
);

await userEvent.click(result.getByTestId('__action__'));
expect(onClick).toHaveBeenCalledTimes(1);
});
});
15 changes: 14 additions & 1 deletion packages/vkui/src/components/Alert/Alert.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,10 @@ export interface AlertProps
*/
dismissButtonTestId?: string;
usePortal?: AppRootPortalProps['usePortal'];
/**
* По умолчанию событие onClick не всплывает
*/
allowClickPropagation?: boolean;
}

/**
Expand All @@ -94,6 +98,8 @@ export const Alert = ({
dismissButtonTestId,
getRootRef,
usePortal,
onClick,
allowClickPropagation = false,
...restProps
}: AlertProps): React.ReactNode => {
const generatedId = React.useId();
Expand Down Expand Up @@ -141,6 +147,13 @@ export const Alert = ({

useScrollLock();

const handleClick = allowClickPropagation
? onClick
: (event: React.MouseEvent<HTMLElement>) => {
stopPropagation(event);
onClick?.(event);
};

return (
<AppRootPortal usePortal={usePortal}>
<PopoutWrapper
Expand All @@ -153,8 +166,8 @@ export const Alert = ({
<FocusTrap
{...restProps}
{...animationHandlers}
onClick={handleClick}
getRootRef={elementRef}
onClick={stopPropagation}
onClose={close}
autoFocus={animationState === 'entered'}
className={classNames(
Expand Down

0 comments on commit 363b5cd

Please sign in to comment.