mirror of
https://github.com/vector-im/element-web.git
synced 2026-05-06 04:36:21 +02:00
fix(docs): fix document editor focus and cursor issues
Three problems prevented the document editor from being usable: 1. The contentEditable div inherited composer sizing (~22px tall) so clicks in the document content area landed outside it and bubbled up to the mx_RoomView div[tabIndex=-1], making the whole room view flash blue instead of focusing the editor. Fixed by making the Editor stack fill the full content area height in _DocumentView.pcss. 2. useSetCursorPosition was not called, so even when the editor received focus there was no selection range and no visible cursor. Added the hook call (same pattern as WysiwygComposer). 3. Any click outside the now-tall contentEditable div (e.g. in padding) still fell through. Added an onClick handler on the content wrapper that calls ref.current.focus() when the click target isn't the editor itself, ensuring any click in the document area focuses the editor. Bonus: suppress the browser default outline on .mx_RoomView:focus so pressing a key no longer shows a blue box around the entire room view. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
parent
2853fefc16
commit
a3875fc854
@ -27,6 +27,11 @@ Please see LICENSE files in the repository root for full details.
|
||||
flex: 1;
|
||||
position: relative;
|
||||
|
||||
/* The outer div has tabIndex=-1 for keyboard shortcuts. Suppress the
|
||||
* browser's default focus outline — the editor's own outline is sufficient. */
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
.mx_MainSplit {
|
||||
flex: 1 1 0;
|
||||
}
|
||||
|
||||
@ -29,16 +29,32 @@ Please see LICENSE files in the repository root for full details.
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: var(--cpd-space-8x) var(--cpd-space-12x);
|
||||
/* Forward clicks anywhere in this area to the editor */
|
||||
cursor: text;
|
||||
|
||||
/* Give the editor enough room to feel like a document */
|
||||
/*
|
||||
* The composer's editor stack is designed for a small multi-line input.
|
||||
* In document mode we want it to fill the full available height so that
|
||||
* clicking anywhere in the content area lands on the contentEditable div.
|
||||
*/
|
||||
.mx_WysiwygComposer_Editor {
|
||||
min-height: 100%;
|
||||
height: 100%;
|
||||
min-height: 400px;
|
||||
|
||||
.mx_WysiwygComposer_Editor_container {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.mx_WysiwygComposer_Editor_content {
|
||||
flex: 1;
|
||||
min-height: 400px;
|
||||
font-size: var(--cpd-font-size-body-lg);
|
||||
line-height: 1.6;
|
||||
caret-color: var(--cpd-color-text-primary);
|
||||
/* Remove the narrow composer outline */
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -14,6 +14,7 @@ import { useMatrixClientContext } from "../../../../contexts/MatrixClientContext
|
||||
import { FormattingButtons } from "./components/FormattingButtons.tsx";
|
||||
import { Editor } from "./components/Editor.tsx";
|
||||
import { ComposerContext, getDefaultContextValue } from "./ComposerContext.ts";
|
||||
import { useSetCursorPosition } from "./hooks/useSetCursorPosition.ts";
|
||||
|
||||
/**
|
||||
* Matrix event type for incremental Automerge deltas sent as timeline events.
|
||||
@ -198,12 +199,29 @@ export const DocumentView = memo(function DocumentView({ room }: DocumentViewPro
|
||||
const { ref, isWysiwygReady, wysiwyg, actionStates } = wysiwygResult;
|
||||
const composerModel = wysiwygResult.composerModel;
|
||||
|
||||
// Place the cursor at the end and focus the editor once the WASM model is
|
||||
// ready. Without this the editor is enabled but has no selection, so no
|
||||
// cursor appears even after the element receives focus.
|
||||
useSetCursorPosition(!isWysiwygReady, ref);
|
||||
|
||||
const { isLoaded, scheduleDeltaSend } = useDocumentSync(room, client, composerModel);
|
||||
|
||||
const handleInput = useCallback(() => {
|
||||
scheduleDeltaSend();
|
||||
}, [scheduleDeltaSend]);
|
||||
|
||||
// Forward clicks anywhere in the content area to the contentEditable so
|
||||
// the user can click anywhere in the document space to start typing.
|
||||
const handleContentClick = useCallback(
|
||||
(ev: React.MouseEvent<HTMLDivElement>) => {
|
||||
// Only forward if the click didn't already land on the editor itself.
|
||||
if (ev.target !== ref.current && ref.current) {
|
||||
ref.current.focus();
|
||||
}
|
||||
},
|
||||
[ref],
|
||||
);
|
||||
|
||||
if (!isLoaded) {
|
||||
return <div className="mx_DocumentView mx_DocumentView_loading" />;
|
||||
}
|
||||
@ -215,7 +233,7 @@ export const DocumentView = memo(function DocumentView({ room }: DocumentViewPro
|
||||
<FormattingButtons composer={wysiwyg} actionStates={actionStates} />
|
||||
</div>
|
||||
{/* eslint-disable-next-line jsx-a11y/no-static-element-interactions */}
|
||||
<div className="mx_DocumentView_content" onInput={handleInput}>
|
||||
<div className="mx_DocumentView_content" onInput={handleInput} onClick={handleContentClick}>
|
||||
<Editor ref={ref} disabled={!isWysiwygReady} placeholder="Start typing your document…" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user