fix: file popover not closing when click inside editor (#924)
This commit is contained in:
@@ -11,7 +11,7 @@ import { observer } from 'mobx-react-lite';
|
|||||||
import { FunctionComponent } from 'preact';
|
import { FunctionComponent } from 'preact';
|
||||||
import { useCallback, useEffect, useRef, useState } from 'preact/hooks';
|
import { useCallback, useEffect, useRef, useState } from 'preact/hooks';
|
||||||
import { Icon } from '../Icon';
|
import { Icon } from '../Icon';
|
||||||
import { useCloseOnClickOutside } from '../utils';
|
import { useCloseOnBlur } from '../utils';
|
||||||
import {
|
import {
|
||||||
ChallengeReason,
|
ChallengeReason,
|
||||||
ContentType,
|
ContentType,
|
||||||
@@ -66,9 +66,7 @@ export const AttachedFilesButton: FunctionComponent<Props> = observer(
|
|||||||
const buttonRef = useRef<HTMLButtonElement>(null);
|
const buttonRef = useRef<HTMLButtonElement>(null);
|
||||||
const panelRef = useRef<HTMLDivElement>(null);
|
const panelRef = useRef<HTMLDivElement>(null);
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
useCloseOnClickOutside(containerRef, () => {
|
const [closeOnBlur, keepMenuOpen] = useCloseOnBlur(containerRef, setOpen);
|
||||||
setOpen(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
const [attachedFilesCount, setAttachedFilesCount] = useState(
|
const [attachedFilesCount, setAttachedFilesCount] = useState(
|
||||||
note ? application.items.getFilesForNote(note).length : 0
|
note ? application.items.getFilesForNote(note).length : 0
|
||||||
@@ -170,7 +168,10 @@ export const AttachedFilesButton: FunctionComponent<Props> = observer(
|
|||||||
const toggleFileProtection = async (file: SNFile) => {
|
const toggleFileProtection = async (file: SNFile) => {
|
||||||
let result: SNFile | undefined;
|
let result: SNFile | undefined;
|
||||||
if (file.protected) {
|
if (file.protected) {
|
||||||
|
keepMenuOpen(true);
|
||||||
result = await application.protections.unprotectFile(file);
|
result = await application.protections.unprotectFile(file);
|
||||||
|
keepMenuOpen(false);
|
||||||
|
buttonRef.current?.focus();
|
||||||
} else {
|
} else {
|
||||||
result = await application.protections.protectFile(file);
|
result = await application.protections.protectFile(file);
|
||||||
}
|
}
|
||||||
@@ -207,10 +208,13 @@ export const AttachedFilesButton: FunctionComponent<Props> = observer(
|
|||||||
file.protected &&
|
file.protected &&
|
||||||
action.type !== PopoverFileItemActionType.ToggleFileProtection
|
action.type !== PopoverFileItemActionType.ToggleFileProtection
|
||||||
) {
|
) {
|
||||||
|
keepMenuOpen(true);
|
||||||
isAuthorizedForAction = await authorizeProtectedActionForFile(
|
isAuthorizedForAction = await authorizeProtectedActionForFile(
|
||||||
file,
|
file,
|
||||||
ChallengeReason.AccessProtectedFile
|
ChallengeReason.AccessProtectedFile
|
||||||
);
|
);
|
||||||
|
keepMenuOpen(false);
|
||||||
|
buttonRef.current?.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isAuthorizedForAction) {
|
if (!isAuthorizedForAction) {
|
||||||
@@ -354,6 +358,7 @@ export const AttachedFilesButton: FunctionComponent<Props> = observer(
|
|||||||
className={`sn-icon-button border-contrast ${
|
className={`sn-icon-button border-contrast ${
|
||||||
attachedFilesCount > 0 ? 'py-1 px-3' : ''
|
attachedFilesCount > 0 ? 'py-1 px-3' : ''
|
||||||
}`}
|
}`}
|
||||||
|
onBlur={closeOnBlur}
|
||||||
>
|
>
|
||||||
<VisuallyHidden>Attached files</VisuallyHidden>
|
<VisuallyHidden>Attached files</VisuallyHidden>
|
||||||
<Icon type="attachment-file" className="block" />
|
<Icon type="attachment-file" className="block" />
|
||||||
@@ -374,6 +379,7 @@ export const AttachedFilesButton: FunctionComponent<Props> = observer(
|
|||||||
maxHeight,
|
maxHeight,
|
||||||
}}
|
}}
|
||||||
className="sn-dropdown sn-dropdown--animated min-w-80 max-h-120 max-w-xs flex flex-col overflow-y-auto fixed"
|
className="sn-dropdown sn-dropdown--animated min-w-80 max-h-120 max-w-xs flex flex-col overflow-y-auto fixed"
|
||||||
|
onBlur={closeOnBlur}
|
||||||
>
|
>
|
||||||
{open && (
|
{open && (
|
||||||
<AttachedFilesPopover
|
<AttachedFilesPopover
|
||||||
@@ -382,6 +388,7 @@ export const AttachedFilesButton: FunctionComponent<Props> = observer(
|
|||||||
note={note}
|
note={note}
|
||||||
handleFileAction={handleFileAction}
|
handleFileAction={handleFileAction}
|
||||||
currentTab={currentTab}
|
currentTab={currentTab}
|
||||||
|
closeOnBlur={closeOnBlur}
|
||||||
setCurrentTab={setCurrentTab}
|
setCurrentTab={setCurrentTab}
|
||||||
isDraggingFiles={isDraggingFiles}
|
isDraggingFiles={isDraggingFiles}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { FOCUSABLE_BUT_NOT_TABBABLE } from '@/constants';
|
||||||
import { WebApplication } from '@/ui_models/application';
|
import { WebApplication } from '@/ui_models/application';
|
||||||
import { AppState } from '@/ui_models/app_state';
|
import { AppState } from '@/ui_models/app_state';
|
||||||
import { ContentType, SNFile, SNNote } from '@standardnotes/snjs';
|
import { ContentType, SNFile, SNNote } from '@standardnotes/snjs';
|
||||||
@@ -22,6 +23,7 @@ type Props = {
|
|||||||
application: WebApplication;
|
application: WebApplication;
|
||||||
appState: AppState;
|
appState: AppState;
|
||||||
currentTab: PopoverTabs;
|
currentTab: PopoverTabs;
|
||||||
|
closeOnBlur: (event: { relatedTarget: EventTarget | null }) => void;
|
||||||
handleFileAction: (action: PopoverFileItemAction) => Promise<boolean>;
|
handleFileAction: (action: PopoverFileItemAction) => Promise<boolean>;
|
||||||
isDraggingFiles: boolean;
|
isDraggingFiles: boolean;
|
||||||
note: SNNote;
|
note: SNNote;
|
||||||
@@ -33,6 +35,7 @@ export const AttachedFilesPopover: FunctionComponent<Props> = observer(
|
|||||||
application,
|
application,
|
||||||
appState,
|
appState,
|
||||||
currentTab,
|
currentTab,
|
||||||
|
closeOnBlur,
|
||||||
handleFileAction,
|
handleFileAction,
|
||||||
isDraggingFiles,
|
isDraggingFiles,
|
||||||
note,
|
note,
|
||||||
@@ -100,6 +103,7 @@ export const AttachedFilesPopover: FunctionComponent<Props> = observer(
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="flex flex-col"
|
className="flex flex-col"
|
||||||
|
tabIndex={FOCUSABLE_BUT_NOT_TABBABLE}
|
||||||
style={{
|
style={{
|
||||||
border: isDraggingFiles
|
border: isDraggingFiles
|
||||||
? '2px dashed var(--sn-stylekit-info-color)'
|
? '2px dashed var(--sn-stylekit-info-color)'
|
||||||
@@ -116,6 +120,7 @@ export const AttachedFilesPopover: FunctionComponent<Props> = observer(
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
setCurrentTab(PopoverTabs.AttachedFiles);
|
setCurrentTab(PopoverTabs.AttachedFiles);
|
||||||
}}
|
}}
|
||||||
|
onBlur={closeOnBlur}
|
||||||
>
|
>
|
||||||
Attached
|
Attached
|
||||||
</button>
|
</button>
|
||||||
@@ -128,6 +133,7 @@ export const AttachedFilesPopover: FunctionComponent<Props> = observer(
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
setCurrentTab(PopoverTabs.AllFiles);
|
setCurrentTab(PopoverTabs.AllFiles);
|
||||||
}}
|
}}
|
||||||
|
onBlur={closeOnBlur}
|
||||||
>
|
>
|
||||||
All files
|
All files
|
||||||
</button>
|
</button>
|
||||||
@@ -144,6 +150,7 @@ export const AttachedFilesPopover: FunctionComponent<Props> = observer(
|
|||||||
onInput={(e) => {
|
onInput={(e) => {
|
||||||
setSearchQuery((e.target as HTMLInputElement).value);
|
setSearchQuery((e.target as HTMLInputElement).value);
|
||||||
}}
|
}}
|
||||||
|
onBlur={closeOnBlur}
|
||||||
/>
|
/>
|
||||||
{searchQuery.length > 0 && (
|
{searchQuery.length > 0 && (
|
||||||
<button
|
<button
|
||||||
@@ -151,6 +158,7 @@ export const AttachedFilesPopover: FunctionComponent<Props> = observer(
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
setSearchQuery('');
|
setSearchQuery('');
|
||||||
}}
|
}}
|
||||||
|
onBlur={closeOnBlur}
|
||||||
>
|
>
|
||||||
<Icon
|
<Icon
|
||||||
type="clear-circle-filled"
|
type="clear-circle-filled"
|
||||||
@@ -170,6 +178,7 @@ export const AttachedFilesPopover: FunctionComponent<Props> = observer(
|
|||||||
isAttachedToNote={attachedFiles.includes(file)}
|
isAttachedToNote={attachedFiles.includes(file)}
|
||||||
handleFileAction={handleFileAction}
|
handleFileAction={handleFileAction}
|
||||||
getIconType={application.iconsController.getIconForFileType}
|
getIconType={application.iconsController.getIconForFileType}
|
||||||
|
closeOnBlur={closeOnBlur}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
@@ -190,7 +199,11 @@ export const AttachedFilesPopover: FunctionComponent<Props> = observer(
|
|||||||
? 'No files attached to this note'
|
? 'No files attached to this note'
|
||||||
: 'No files found in this account'}
|
: 'No files found in this account'}
|
||||||
</div>
|
</div>
|
||||||
<Button type="normal" onClick={handleAttachFilesClick}>
|
<Button
|
||||||
|
type="normal"
|
||||||
|
onClick={handleAttachFilesClick}
|
||||||
|
onBlur={closeOnBlur}
|
||||||
|
>
|
||||||
{currentTab === PopoverTabs.AttachedFiles ? 'Attach' : 'Upload'}{' '}
|
{currentTab === PopoverTabs.AttachedFiles ? 'Attach' : 'Upload'}{' '}
|
||||||
files
|
files
|
||||||
</Button>
|
</Button>
|
||||||
@@ -204,6 +217,7 @@ export const AttachedFilesPopover: FunctionComponent<Props> = observer(
|
|||||||
<button
|
<button
|
||||||
className="sn-dropdown-item py-3 border-0 border-t-1px border-solid border-main focus:bg-info-backdrop"
|
className="sn-dropdown-item py-3 border-0 border-t-1px border-solid border-main focus:bg-info-backdrop"
|
||||||
onClick={handleAttachFilesClick}
|
onClick={handleAttachFilesClick}
|
||||||
|
onBlur={closeOnBlur}
|
||||||
>
|
>
|
||||||
<Icon type="add" className="mr-2 color-neutral" />
|
<Icon type="add" className="mr-2 color-neutral" />
|
||||||
{currentTab === PopoverTabs.AttachedFiles
|
{currentTab === PopoverTabs.AttachedFiles
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ export type PopoverFileItemProps = {
|
|||||||
isAttachedToNote: boolean;
|
isAttachedToNote: boolean;
|
||||||
handleFileAction: (action: PopoverFileItemAction) => Promise<boolean>;
|
handleFileAction: (action: PopoverFileItemAction) => Promise<boolean>;
|
||||||
getIconType(type: string): IconType;
|
getIconType(type: string): IconType;
|
||||||
|
closeOnBlur: (event: { relatedTarget: EventTarget | null }) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const PopoverFileItem: FunctionComponent<PopoverFileItemProps> = ({
|
export const PopoverFileItem: FunctionComponent<PopoverFileItemProps> = ({
|
||||||
@@ -28,6 +29,7 @@ export const PopoverFileItem: FunctionComponent<PopoverFileItemProps> = ({
|
|||||||
isAttachedToNote,
|
isAttachedToNote,
|
||||||
handleFileAction,
|
handleFileAction,
|
||||||
getIconType,
|
getIconType,
|
||||||
|
closeOnBlur,
|
||||||
}) => {
|
}) => {
|
||||||
const [fileName, setFileName] = useState(file.name);
|
const [fileName, setFileName] = useState(file.name);
|
||||||
const [isRenamingFile, setIsRenamingFile] = useState(false);
|
const [isRenamingFile, setIsRenamingFile] = useState(false);
|
||||||
@@ -93,6 +95,7 @@ export const PopoverFileItem: FunctionComponent<PopoverFileItemProps> = ({
|
|||||||
isAttachedToNote={isAttachedToNote}
|
isAttachedToNote={isAttachedToNote}
|
||||||
handleFileAction={handleFileAction}
|
handleFileAction={handleFileAction}
|
||||||
setIsRenamingFile={setIsRenamingFile}
|
setIsRenamingFile={setIsRenamingFile}
|
||||||
|
closeOnBlur={closeOnBlur}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ type ButtonProps = {
|
|||||||
| TargetedEvent<HTMLFormElement>
|
| TargetedEvent<HTMLFormElement>
|
||||||
| TargetedMouseEvent<HTMLButtonElement>
|
| TargetedMouseEvent<HTMLButtonElement>
|
||||||
) => void;
|
) => void;
|
||||||
|
onBlur?: (event: FocusEvent) => void;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -34,6 +35,7 @@ export const Button: FunctionComponent<ButtonProps> = forwardRef(
|
|||||||
type,
|
type,
|
||||||
label,
|
label,
|
||||||
className = '',
|
className = '',
|
||||||
|
onBlur,
|
||||||
onClick,
|
onClick,
|
||||||
disabled = false,
|
disabled = false,
|
||||||
children,
|
children,
|
||||||
@@ -46,6 +48,7 @@ export const Button: FunctionComponent<ButtonProps> = forwardRef(
|
|||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
className={`${buttonClass} ${cursorClass} ${className}`}
|
className={`${buttonClass} ${cursorClass} ${className}`}
|
||||||
|
onBlur={onBlur}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
onClick(e);
|
onClick(e);
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|||||||
Reference in New Issue
Block a user