diff --git a/src/explorer/Explorer.tsx b/src/explorer/Explorer.tsx index 1d4a0497..39e8b90b 100644 --- a/src/explorer/Explorer.tsx +++ b/src/explorer/Explorer.tsx @@ -26,6 +26,7 @@ import { fileStorageExportFile, } from '../fileStorage/actions'; import { useSelector } from '../reducers'; +import { preventBrowserNativeContextMenu, preventFocusOnClick } from '../utils/react'; import NewFileWizard from './NewFileWizard'; import RenameFileDialog from './RenameFileDialog'; import { explorerDeleteFile, explorerImportFiles } from './actions'; @@ -53,8 +54,7 @@ const ActionButton: React.VoidFunctionComponent = (props) => icon={props.icon} title={i18n.translate(props.toolTipId, props.toolTipReplacements)} disabled={props.disabled} - // prevent focus on click - onMouseDown={(e) => e.preventDefault()} + onMouseDown={preventFocusOnClick} onClick={() => props.onClick()} /> ); @@ -208,7 +208,7 @@ const FileTree: React.VFC = () => { const Explorer: React.VFC = () => { return ( -
e.preventDefault()}> +
diff --git a/src/explorer/NewFileWizard.tsx b/src/explorer/NewFileWizard.tsx index 3dad311b..ad2db0fb 100644 --- a/src/explorer/NewFileWizard.tsx +++ b/src/explorer/NewFileWizard.tsx @@ -16,6 +16,7 @@ import { FileNameValidationResult, pythonFileExtension, } from '../pybricksMicropython/lib'; +import { preventFocusOnClick } from '../utils/react'; import FileNameFormGroup from './FileNameFormGroup'; import { Hub, explorerCreateNewFile } from './actions'; import { NewFileWizardStringId } from './i18n'; @@ -80,7 +81,7 @@ const NewFileWizard: React.VoidFunctionComponent = (props) = aria-label="Create" intent="primary" disabled={fileNameValidation !== FileNameValidationResult.IsOk} - onMouseDown={(e) => e.preventDefault()} + onMouseDown={preventFocusOnClick} onClick={() => { props.onClose(); dispatch( diff --git a/src/explorer/RenameFileDialog.tsx b/src/explorer/RenameFileDialog.tsx index 34f293f6..e77a8c7d 100644 --- a/src/explorer/RenameFileDialog.tsx +++ b/src/explorer/RenameFileDialog.tsx @@ -7,6 +7,7 @@ import React, { useRef, useState } from 'react'; import { useDispatch } from 'react-redux'; import { fileStorageRenameFile } from '../fileStorage/actions'; import { FileNameValidationResult } from '../pybricksMicropython/lib'; +import { preventFocusOnClick } from '../utils/react'; import FileNameFormGroup from './FileNameFormGroup'; import { RenameFileStringId } from './i18n'; import en from './i18n.en.json'; @@ -61,7 +62,7 @@ const RenameFileDialog: React.VoidFunctionComponent = ( aria-label="Rename" intent="primary" disabled={result !== FileNameValidationResult.IsOk} - onMouseDown={(e) => e.preventDefault()} + onMouseDown={preventFocusOnClick} onClick={() => { props.onClose(); dispatch( diff --git a/src/status-bar/StatusBar.tsx b/src/status-bar/StatusBar.tsx index fbca4972..a2a083e8 100644 --- a/src/status-bar/StatusBar.tsx +++ b/src/status-bar/StatusBar.tsx @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2020-2021 The Pybricks Authors +// Copyright (c) 2020-2022 The Pybricks Authors import { Button, Intent, ProgressBar } from '@blueprintjs/core'; import { Classes as Classes2, Popover2, Popover2Props } from '@blueprintjs/popover2'; @@ -7,6 +7,7 @@ import { useI18n } from '@shopify/react-i18n'; import React from 'react'; import { BleConnectionState } from '../ble/reducers'; import { useSelector } from '../reducers'; +import { preventBrowserNativeContextMenu, preventFocusOnClick } from '../utils/react'; import { MessageId } from './i18n'; import en from './i18n.en.json'; @@ -61,7 +62,7 @@ const HubInfoButton: React.VFC = (_props) => { @@ -112,7 +113,7 @@ const StatusBar: React.VFC = (_props) => { className="pb-status-bar" role="status" aria-live="off" - onContextMenu={(e): void => e.preventDefault()} + onContextMenu={preventBrowserNativeContextMenu} > {connection === BleConnectionState.Connected && ( <> diff --git a/src/toolbar/ActionButton.tsx b/src/toolbar/ActionButton.tsx index b415a0c5..3550bb5c 100644 --- a/src/toolbar/ActionButton.tsx +++ b/src/toolbar/ActionButton.tsx @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2020-2021 The Pybricks Authors +// Copyright (c) 2020-2022 The Pybricks Authors import { Button, @@ -13,6 +13,7 @@ import { Tooltip2 } from '@blueprintjs/popover2'; import { useI18n } from '@shopify/react-i18n'; import React, { useEffect, useMemo, useState } from 'react'; import { tooltipDelay } from '../app/constants'; +import { preventFocusOnClick } from '../utils/react'; import { TooltipId } from './i18n'; import en from './i18n.en.json'; @@ -104,10 +105,7 @@ const ActionButton: React.FC = (props) => { elementRef={tooltipTargetRef as IRef} {...tooltipTargetProps} intent={Intent.PRIMARY} - onMouseDown={(e) => { - // prevent focus from mouse click - e.preventDefault(); - }} + onMouseDown={preventFocusOnClick} onClick={() => props.onAction()} disabled={props.enabled === false} style={ diff --git a/src/toolbar/OpenFileButton.tsx b/src/toolbar/OpenFileButton.tsx index 680fb837..73a1a5c4 100644 --- a/src/toolbar/OpenFileButton.tsx +++ b/src/toolbar/OpenFileButton.tsx @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2020-2021 The Pybricks Authors +// Copyright (c) 2020-2022 The Pybricks Authors import { Button, IRef, Intent, Spinner, SpinnerSize } from '@blueprintjs/core'; import { Tooltip2 } from '@blueprintjs/popover2'; @@ -7,6 +7,7 @@ import { useI18n } from '@shopify/react-i18n'; import React, { useEffect, useState } from 'react'; import { useDropzone } from 'react-dropzone'; import { tooltipDelay } from '../app/constants'; +import { preventFocusOnClick } from '../utils/react'; import { TooltipId } from './i18n'; import en from './i18n.en.json'; @@ -123,10 +124,7 @@ const OpenFileButton: React.FC = (props) => { props.enabled === false ? { pointerEvents: 'none' } : undefined, - onMouseDown: (e) => { - // prevent focus from mouse click - e.preventDefault(); - }, + onMouseDown: preventFocusOnClick, onClick: props.onClick, })} > diff --git a/src/toolbar/Toolbar.tsx b/src/toolbar/Toolbar.tsx index 10cbda92..bf35ab3e 100644 --- a/src/toolbar/Toolbar.tsx +++ b/src/toolbar/Toolbar.tsx @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2020-2021 The Pybricks Authors +// Copyright (c) 2020-2022 The Pybricks Authors import { ButtonGroup } from '@blueprintjs/core'; import React, { useState } from 'react'; @@ -12,6 +12,7 @@ import RunButton from '../hub/RunButton'; import StopButton from '../hub/StopButton'; import SettingsButton from '../settings/SettingsButton'; import SettingsDrawer from '../settings/SettingsDrawer'; +import { preventBrowserNativeContextMenu } from '../utils/react'; import './toolbar.scss'; @@ -21,7 +22,7 @@ const Toolbar: React.VFC = (_props) => { return (
e.preventDefault()} + onContextMenu={preventBrowserNativeContextMenu} className="pb-toolbar" > diff --git a/src/utils/react.ts b/src/utils/react.ts new file mode 100644 index 00000000..1bc7ea70 --- /dev/null +++ b/src/utils/react.ts @@ -0,0 +1,16 @@ +// helper functions for React components + +import React from 'react'; + +/** + * Callback that can be passed to onMouseDown event handlers to prevent + * an element from becoming focused when clicked. + */ +export const preventFocusOnClick: React.MouseEventHandler = (e) => e.preventDefault(); + +/** + * Callback that can be passed to onContextMenu event handlers to prevent + * the native browser context menu from being shown. + */ +export const preventBrowserNativeContextMenu: React.MouseEventHandler = (e) => + e.preventDefault();