diff --git a/packages/frontend/core/src/components/affine/auth/user-plan-button.tsx b/packages/frontend/core/src/components/affine/auth/user-plan-button.tsx index 212e3b167..eced39a3a 100644 --- a/packages/frontend/core/src/components/affine/auth/user-plan-button.tsx +++ b/packages/frontend/core/src/components/affine/auth/user-plan-button.tsx @@ -1,9 +1,10 @@ import { Tooltip } from '@affine/component/ui/tooltip'; +import { useCatchEventCallback } from '@affine/core/hooks/use-catch-event-hook'; import { SubscriptionPlan } from '@affine/graphql'; import { useI18n } from '@affine/i18n'; import { useLiveData, useServices } from '@toeverything/infra'; import { useSetAtom } from 'jotai'; -import { useCallback, useEffect } from 'react'; +import { useEffect } from 'react'; import { openSettingModalAtom } from '../../../atoms'; import { @@ -35,17 +36,13 @@ export const UserPlanButton = () => { }, [subscriptionService]); const setSettingModalAtom = useSetAtom(openSettingModalAtom); - const handleClick = useCallback( - (e: React.MouseEvent) => { - e.stopPropagation(); - setSettingModalAtom({ - open: true, - activeTab: 'plans', - scrollAnchor: 'cloudPricingPlan', - }); - }, - [setSettingModalAtom] - ); + const handleClick = useCatchEventCallback(() => { + setSettingModalAtom({ + open: true, + activeTab: 'plans', + scrollAnchor: 'cloudPricingPlan', + }); + }, [setSettingModalAtom]); const t = useI18n(); diff --git a/packages/frontend/core/src/components/affine/setting-modal/account-setting/index.tsx b/packages/frontend/core/src/components/affine/setting-modal/account-setting/index.tsx index 003720af9..4fc6e73a0 100644 --- a/packages/frontend/core/src/components/affine/setting-modal/account-setting/index.tsx +++ b/packages/frontend/core/src/components/affine/setting-modal/account-setting/index.tsx @@ -6,6 +6,7 @@ import { import { Avatar } from '@affine/component/ui/avatar'; import { Button } from '@affine/component/ui/button'; import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks'; +import { useCatchEventCallback } from '@affine/core/hooks/use-catch-event-hook'; import { track } from '@affine/core/mixpanel'; import { SubscriptionPlan } from '@affine/graphql'; import { useI18n } from '@affine/i18n'; @@ -17,7 +18,7 @@ import { useServices, } from '@toeverything/infra'; import { useSetAtom } from 'jotai'; -import type { FC, MouseEvent } from 'react'; +import type { FC } from 'react'; import { useCallback, useEffect, useState } from 'react'; import { @@ -53,14 +54,10 @@ export const UserAvatar = () => { [session] ); - const handleRemoveUserAvatar = useAsyncCallback( - async (e: MouseEvent) => { - track.$.settingsPanel.accountSettings.removeAvatar(); - e.stopPropagation(); - await session.removeAvatar(); - }, - [session] - ); + const handleRemoveUserAvatar = useCatchEventCallback(async () => { + track.$.settingsPanel.accountSettings.removeAvatar(); + await session.removeAvatar(); + }, [session]); return ( { handleUpdateWorkspaceName(input); }, [handleUpdateWorkspaceName, input]); - const handleRemoveUserAvatar = useAsyncCallback( - async (e: MouseEvent) => { - e.stopPropagation(); - await setWorkspaceAvatar(null); - }, - [setWorkspaceAvatar] - ); + const handleRemoveUserAvatar = useCatchEventCallback(async () => { + await setWorkspaceAvatar(null); + }, [setWorkspaceAvatar]); const handleUploadAvatar = useCallback( (file: File) => { diff --git a/packages/frontend/core/src/components/app-sidebar/app-download-button/index.tsx b/packages/frontend/core/src/components/app-sidebar/app-download-button/index.tsx index 153cc2f02..dd71c0ac7 100644 --- a/packages/frontend/core/src/components/app-sidebar/app-download-button/index.tsx +++ b/packages/frontend/core/src/components/app-sidebar/app-download-button/index.tsx @@ -1,3 +1,4 @@ +import { useCatchEventCallback } from '@affine/core/hooks/use-catch-event-hook'; import { track } from '@affine/core/mixpanel'; import { CloseIcon, DownloadIcon } from '@blocksuite/icons/rc'; import clsx from 'clsx'; @@ -15,7 +16,7 @@ export function AppDownloadButton({ }) { const [show, setShow] = useState(true); - const handleClose = useCallback(() => { + const handleClose = useCatchEventCallback(() => { setShow(false); }, []); @@ -39,13 +40,7 @@ export function AppDownloadButton({ Download App -
{ - e.stopPropagation(); - handleClose(); - }} - > +
diff --git a/packages/frontend/core/src/components/app-sidebar/app-updater-button/index.tsx b/packages/frontend/core/src/components/app-sidebar/app-updater-button/index.tsx index 8a1c709fd..a7907df24 100644 --- a/packages/frontend/core/src/components/app-sidebar/app-updater-button/index.tsx +++ b/packages/frontend/core/src/components/app-sidebar/app-updater-button/index.tsx @@ -1,4 +1,5 @@ import { Tooltip } from '@affine/component'; +import { useCatchEventCallback } from '@affine/core/hooks/use-catch-event-hook'; import { popupWindow } from '@affine/core/utils'; import { Unreachable } from '@affine/env/constant'; import { useI18n } from '@affine/i18n'; @@ -118,13 +119,9 @@ function OpenDownloadPage({ updateAvailable }: ButtonContentProps) { function WhatsNew({ onDismissChangelog }: ButtonContentProps) { const t = useI18n(); - const onClickClose: React.MouseEventHandler = useCallback( - e => { - onDismissChangelog(); - e.stopPropagation(); - }, - [onDismissChangelog] - ); + const onClickClose = useCatchEventCallback(() => { + onDismissChangelog(); + }, [onDismissChangelog]); return ( <>
diff --git a/packages/frontend/core/src/components/page-list/docs/page-tags.tsx b/packages/frontend/core/src/components/page-list/docs/page-tags.tsx index fd4a1d347..a903a32da 100644 --- a/packages/frontend/core/src/components/page-list/docs/page-tags.tsx +++ b/packages/frontend/core/src/components/page-list/docs/page-tags.tsx @@ -1,11 +1,11 @@ import { Menu } from '@affine/component'; +import { useCatchEventCallback } from '@affine/core/hooks/use-catch-event-hook'; import type { Tag } from '@affine/core/modules/tag'; import { CloseIcon, MoreHorizontalIcon } from '@blocksuite/icons/rc'; import { LiveData, useLiveData } from '@toeverything/infra'; import { assignInlineVars } from '@vanilla-extract/dynamic'; import clsx from 'clsx'; -import type { MouseEventHandler } from 'react'; -import { useCallback, useMemo } from 'react'; +import { useMemo } from 'react'; import { stopPropagation } from '../utils'; import * as styles from './page-tags.css'; @@ -62,13 +62,9 @@ export const TagItem = ({ }: TagItemProps) => { const value = useLiveData(tag?.value$); const color = useLiveData(tag?.color$); - const handleRemove: MouseEventHandler = useCallback( - e => { - e.stopPropagation(); - onRemoved?.(); - }, - [onRemoved] - ); + const handleRemove = useCatchEventCallback(() => { + onRemoved?.(); + }, [onRemoved]); return (
) => { if (e.key === 'Escape') { if (isNameEmpty) { @@ -83,7 +84,6 @@ export const CreateCollection = ({ e.currentTarget.blur(); } } - e.stopPropagation(); }, [isNameEmpty] ); diff --git a/packages/frontend/core/src/hooks/affine-async-hooks.ts b/packages/frontend/core/src/hooks/affine-async-hooks.ts index 2c31c5f7c..c1d9679fc 100644 --- a/packages/frontend/core/src/hooks/affine-async-hooks.ts +++ b/packages/frontend/core/src/hooks/affine-async-hooks.ts @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { type DependencyList } from 'react'; export type AsyncErrorHandler = (error: Error) => void; @@ -17,7 +17,7 @@ export const AsyncCallbackContext = React.createContext( */ export function useAsyncCallback( callback: (...args: T) => Promise, - deps: any[] + deps: DependencyList ): (...args: T) => void { const handleAsyncError = React.useContext(AsyncCallbackContext); return React.useCallback( diff --git a/packages/frontend/core/src/hooks/use-catch-event-hook.ts b/packages/frontend/core/src/hooks/use-catch-event-hook.ts new file mode 100644 index 000000000..0d71f4ddb --- /dev/null +++ b/packages/frontend/core/src/hooks/use-catch-event-hook.ts @@ -0,0 +1,17 @@ +import { type DependencyList, type SyntheticEvent } from 'react'; + +import { useAsyncCallback } from './affine-async-hooks'; + +export const useCatchEventCallback = ( + cb: (e: E) => void | Promise, + deps: DependencyList +) => { + return useAsyncCallback( + async (e: E) => { + e.stopPropagation(); + await cb(e); + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + deps + ); +}; diff --git a/packages/frontend/core/src/modules/app-tabs-header/views/app-tabs-header.tsx b/packages/frontend/core/src/modules/app-tabs-header/views/app-tabs-header.tsx index 81bd5f628..1f21f2b0b 100644 --- a/packages/frontend/core/src/modules/app-tabs-header/views/app-tabs-header.tsx +++ b/packages/frontend/core/src/modules/app-tabs-header/views/app-tabs-header.tsx @@ -14,6 +14,7 @@ import { import { appSidebarWidthAtom } from '@affine/core/components/app-sidebar/index.jotai'; import { WindowsAppControls } from '@affine/core/components/pure/header/windows-app-controls'; import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks'; +import { useCatchEventCallback } from '@affine/core/hooks/use-catch-event-hook'; import type { AffineDNDData } from '@affine/core/types/dnd'; import { apis, events } from '@affine/electron-api'; import { useI18n } from '@affine/i18n'; @@ -92,15 +93,19 @@ const WorkbenchTab = ({ }, [tabsHeaderService, workbench.id] ); - const onCloseTab: MouseEventHandler = useAsyncCallback( + const handleAuxClick: MouseEventHandler = useCatchEventCallback( async e => { - e.stopPropagation(); - - await tabsHeaderService.closeTab?.(workbench.id); + if (e.button === 1) { + await tabsHeaderService.closeTab?.(workbench.id); + } }, [tabsHeaderService, workbench.id] ); + const handleCloseTab = useCatchEventCallback(async () => { + await tabsHeaderService.closeTab?.(workbench.id); + }, [tabsHeaderService, workbench.id]); + const { dropTargetRef, closestEdge } = useDropTarget( () => ({ closestEdge: { @@ -154,6 +159,7 @@ const WorkbenchTab = ({ onContextMenu={() => { onContextMenu(viewIdx); }} + onAuxClick={handleAuxClick} onClick={e => { e.stopPropagation(); onActivateView(viewIdx); @@ -185,7 +191,7 @@ const WorkbenchTab = ({ diff --git a/packages/frontend/core/src/modules/workbench/view/workbench-link.tsx b/packages/frontend/core/src/modules/workbench/view/workbench-link.tsx index 75d26cc73..16eec059d 100644 --- a/packages/frontend/core/src/modules/workbench/view/workbench-link.tsx +++ b/packages/frontend/core/src/modules/workbench/view/workbench-link.tsx @@ -1,5 +1,5 @@ import { useAppSettingHelper } from '@affine/core/hooks/affine/use-app-setting-helper'; -import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks'; +import { useCatchEventCallback } from '@affine/core/hooks/use-catch-event-hook'; import { useLiveData, useService } from '@toeverything/infra'; import { type To } from 'history'; import { forwardRef, type MouseEvent } from 'react'; @@ -21,10 +21,9 @@ export const WorkbenchLink = forwardRef< const link = basename + (typeof to === 'string' ? to : `${to.pathname}${to.search}${to.hash}`); - const handleClick = useAsyncCallback( + const handleClick = useCatchEventCallback( async (event: React.MouseEvent) => { event.preventDefault(); - event.stopPropagation(); if (onClick?.(event) === false) { return; }