diff --git a/app/components/Input.tsx b/app/components/Input.tsx index 77b748bff1..1c48bc0552 100644 --- a/app/components/Input.tsx +++ b/app/components/Input.tsx @@ -95,7 +95,7 @@ const IconWrapper = styled.span` export const Outline = styled(Flex)<{ margin?: string | number; hasError?: boolean; - focused?: boolean; + $focused?: boolean; }>` flex: 1; margin: ${(props) => @@ -106,7 +106,7 @@ export const Outline = styled(Flex)<{ border-color: ${(props) => props.hasError ? props.theme.danger - : props.focused + : props.$focused ? props.theme.inputBorderFocused : props.theme.inputBorder}; border-radius: 4px; @@ -224,7 +224,7 @@ function Input( ) : ( wrappedLabel ))} - + {prefix} {icon && {icon}} {type === "textarea" ? ( diff --git a/app/editor/components/LinkEditor.tsx b/app/editor/components/LinkEditor.tsx index 22946641b3..d6dd4b0c8b 100644 --- a/app/editor/components/LinkEditor.tsx +++ b/app/editor/components/LinkEditor.tsx @@ -33,6 +33,7 @@ type Props = { mark?: Mark; dictionary: Dictionary; view: EditorView; + autoFocus?: boolean; onLinkAdd: () => void; onLinkUpdate: () => void; onLinkRemove: () => void; @@ -45,6 +46,7 @@ const LinkEditor: React.FC = ({ mark, dictionary, view, + autoFocus, onLinkAdd, onLinkUpdate, onLinkRemove, @@ -209,7 +211,7 @@ const LinkEditor: React.FC = ({ onKeyDown={handleKeyDown} onChange={handleSearch} onFocus={handleSearch} - autoFocus={getHref() === ""} + autoFocus={autoFocus} readOnly={!view.editable} /> {actions.map((action, index) => { diff --git a/app/editor/components/SelectionToolbar.tsx b/app/editor/components/SelectionToolbar.tsx index 798f984344..cf7b80bf7d 100644 --- a/app/editor/components/SelectionToolbar.tsx +++ b/app/editor/components/SelectionToolbar.tsx @@ -87,25 +87,29 @@ export function SelectionToolbar(props: Props) { const isMobile = useMobile(); const isActive = props.isActive || isMobile; const { state } = view; + const [autoFocusLinkInput, setAutoFocusLinkInput] = React.useState(false); const isDragging = useIsDragging(state); const { selection } = state; const [activeToolbar, setActiveToolbar] = React.useState( null ); - React.useEffect(() => { - const { selection } = state; - const linkMark = - selection instanceof NodeSelection - ? getMarkRangeNodeSelection(selection, state.schema.marks.link) - : getMarkRange(selection.$from, state.schema.marks.link); + const linkMark = + selection instanceof NodeSelection + ? getMarkRangeNodeSelection(selection, state.schema.marks.link) + : getMarkRange(selection.$from, state.schema.marks.link); - const isEmbedSelection = - selection instanceof NodeSelection && - selection.node.type.name === "embed"; + const isEmbedSelection = + selection instanceof NodeSelection && selection.node.type.name === "embed"; - const isCodeSelection = isInCode(state, { onlyBlock: true }); - const isNoticeSelection = isInNotice(state); + const isCodeSelection = isInCode(state, { onlyBlock: true }); + const isNoticeSelection = isInNotice(state); + + React.useLayoutEffect(() => { + if (!isActive) { + setActiveToolbar(null); + return; + } if (isEmbedSelection && !readOnly) { setActiveToolbar(Toolbar.Media); @@ -124,22 +128,37 @@ export function SelectionToolbar(props: Props) { } else if (selection.empty) { setActiveToolbar(null); } - }, [readOnly, selection]); + }, [ + readOnly, + isActive, + selection, + linkMark, + isEmbedSelection, + isCodeSelection, + isNoticeSelection, + ]); + + React.useLayoutEffect(() => { + if (autoFocusLinkInput && activeToolbar !== Toolbar.Link) { + setAutoFocusLinkInput(false); + } + }, [activeToolbar]); // Refocus the editor when the link toolbar closes to prevent focus loss const prevActiveToolbar = React.useRef(activeToolbar); - React.useEffect(() => { + React.useLayoutEffect(() => { if ( prevActiveToolbar.current === Toolbar.Link && activeToolbar !== Toolbar.Link && - !readOnly + !readOnly && + isActive ) { view.focus(); } prevActiveToolbar.current = activeToolbar; - }, [activeToolbar, readOnly, view]); + }, [activeToolbar, readOnly, isActive, view]); - React.useEffect(() => { + React.useLayoutEffect(() => { const handleClickOutside = (ev: MouseEvent): void => { if ( ev.target instanceof HTMLElement && @@ -193,11 +212,10 @@ export function SelectionToolbar(props: Props) { ) { ev.preventDefault(); ev.stopPropagation(); - if (activeToolbar === Toolbar.Link) { - setActiveToolbar(Toolbar.Menu); - } else if (activeToolbar === Toolbar.Menu) { - setActiveToolbar(Toolbar.Link); - } + setAutoFocusLinkInput(true); + setActiveToolbar( + activeToolbar === Toolbar.Link ? Toolbar.Menu : Toolbar.Link + ); } }, view.dom, @@ -218,12 +236,6 @@ export function SelectionToolbar(props: Props) { const isAttachmentSelection = selection instanceof NodeSelection && selection.node.type.name === "attachment"; - const isCodeSelection = isInCode(state, { onlyBlock: true }); - const isNoticeSelection = isInNotice(state); - const link = - selection instanceof NodeSelection - ? getMarkRangeNodeSelection(selection, state.schema.marks.link) - : getMarkRange(selection.$from, state.schema.marks.link); let items: MenuItem[] = []; let align: "center" | "start" | "end" = "center"; @@ -289,6 +301,7 @@ export function SelectionToolbar(props: Props) { if (item.name === "linkOnImage" || item.name === "addLink") { item.onClick = () => { + setAutoFocusLinkInput(true); setActiveToolbar(Toolbar.Link); }; } @@ -315,10 +328,11 @@ export function SelectionToolbar(props: Props) { > {activeToolbar === Toolbar.Link ? ( setActiveToolbar(null)} onLinkUpdate={() => setActiveToolbar(null)} onLinkRemove={() => setActiveToolbar(null)} @@ -328,7 +342,7 @@ export function SelectionToolbar(props: Props) { /> ) : activeToolbar === Toolbar.Media ? ( )}