Skip to content
This repository was archived by the owner on Feb 6, 2023. It is now read-only.

Fixes scroll problems for huge blocks #1598

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/component/base/DraftEditor.react.js
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,7 @@ class DraftEditor extends React.Component<DraftEditorProps, State> {
editorState,
key: 'contents' + this.state.contentsKey,
textDirectionality,
editor: this,
};

return (
Expand Down
68 changes: 41 additions & 27 deletions src/component/contents/DraftEditorBlock.react.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import type {DraftInlineStyle} from 'DraftInlineStyle';
import type SelectionState from 'SelectionState';
import type {BidiDirection} from 'UnicodeBidiDirection';
import type {List} from 'immutable';
import type DraftEditor from 'DraftEditor.react';

const DraftEditorLeaf = require('DraftEditorLeaf.react');
const DraftOffsetKey = require('DraftOffsetKey');
Expand All @@ -32,7 +33,6 @@ const cx = require('cx');
const getElementPosition = require('getElementPosition');
const getScrollPosition = require('getScrollPosition');
const getViewportDimensions = require('getViewportDimensions');
const invariant = require('invariant');
const nullthrows = require('nullthrows');

const SCROLL_BUFFER = 10;
Expand All @@ -51,6 +51,7 @@ type Props = {
selection: SelectionState,
startIndent?: boolean,
tree: List<any>,
editor: DraftEditor,
};

/**
Expand Down Expand Up @@ -96,6 +97,7 @@ class DraftEditorBlock extends React.Component<Props> {
*/
componentDidMount(): void {
const selection = this.props.selection;
const editor = this.props.editor;
const endKey = selection.getEndKey();
if (!selection.getHasFocus() || endKey !== this.props.block.getKey()) {
return;
Expand All @@ -105,34 +107,46 @@ class DraftEditorBlock extends React.Component<Props> {
if (blockNode == null) {
return;
}
const scrollParent = Style.getScrollParent(blockNode);
const editorNode = ReactDOM.findDOMNode(editor);
let scrollParent = Style.getScrollParent(blockNode);
const scrollPosition = getScrollPosition(scrollParent);
let scrollDelta;

if (scrollParent === window) {
const nodePosition = getElementPosition(blockNode);
const nodeBottom = nodePosition.y + nodePosition.height;
const viewportHeight = getViewportDimensions().height;
scrollDelta = nodeBottom - viewportHeight;
if (scrollDelta > 0) {
window.scrollTo(
scrollPosition.x,
scrollPosition.y + scrollDelta + SCROLL_BUFFER,
);
}
} else {
invariant(
blockNode instanceof HTMLElement,
'blockNode is not an HTMLElement',
const blockPosition = getElementPosition(blockNode);
const editorPosition = getElementPosition(editorNode);
const isScrParentWindow = scrollParent === window;
const viewportHeight = getViewportDimensions().height;
if (isScrParentWindow) {
scrollParent = window.document.body;
}
const scrollParentPosition = !isScrParentWindow
? getElementPosition(scrollParent)
: {y: 0, height: viewportHeight};

//Fix issue #304
const blockHeight = blockPosition.height;
const blockTop = blockPosition.y - editorPosition.y;
const blockBottom = blockTop + blockHeight;
//viewport of editor:
const visTop = scrollParentPosition.y - editorPosition.y;
const visHeight = scrollParentPosition.height;
const visBottom = visTop + visHeight;
let scrollDeltaTop = visTop - blockTop;
let scrollDeltaBottom = visBottom - blockBottom;
const isBigBlock = blockHeight >= visHeight;
//for big block scroll to its top
let correctScrollTop = undefined;
if (visTop > blockTop || (isBigBlock && blockTop > visBottom)) {
correctScrollTop = Math.max(
0,
scrollPosition.y - SCROLL_BUFFER - scrollDeltaTop,
);
const blockBottom = blockNode.offsetHeight + blockNode.offsetTop;
const scrollBottom = scrollParent.offsetHeight + scrollPosition.y;
scrollDelta = blockBottom - scrollBottom;
if (scrollDelta > 0) {
Scroll.setTop(
scrollParent,
Scroll.getTop(scrollParent) + scrollDelta + SCROLL_BUFFER,
);
} else if (!isBigBlock && blockBottom > visBottom) {
correctScrollTop = scrollPosition.y + SCROLL_BUFFER - scrollDeltaBottom;
}
if (correctScrollTop !== undefined) {
if (isScrParentWindow) {
window.scrollTo(scrollPosition.x, correctScrollTop);
} else {
Scroll.setTop(scrollParent, correctScrollTop);
}
}
}
Expand Down
4 changes: 4 additions & 0 deletions src/component/contents/DraftEditorContents-core.react.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import type {DraftBlockRenderMap} from 'DraftBlockRenderMap';
import type {DraftInlineStyle} from 'DraftInlineStyle';
import type EditorState from 'EditorState';
import type {BidiDirection} from 'UnicodeBidiDirection';
import type DraftEditor from 'DraftEditor.react';

const DraftEditorBlock = require('DraftEditorBlock.react');
const DraftOffsetKey = require('DraftOffsetKey');
Expand All @@ -37,6 +38,7 @@ type Props = {
editorKey?: string,
editorState: EditorState,
textDirectionality?: BidiDirection,
editor: DraftEditor,
};

/**
Expand Down Expand Up @@ -133,6 +135,7 @@ class DraftEditorContents extends React.Component<Props> {
editorState,
editorKey,
textDirectionality,
editor,
} = this.props;

const content = editorState.getCurrentContent();
Expand Down Expand Up @@ -178,6 +181,7 @@ class DraftEditorContents extends React.Component<Props> {
offsetKey,
selection,
tree: editorState.getBlockTree(key),
editor,
};

const configForType =
Expand Down
90 changes: 83 additions & 7 deletions src/component/contents/__tests__/DraftEditorBlock.react-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,17 @@ const getHelloBlock = () => {
});
};

const getHugeBlock = () => {
return new ContentBlock({
key: 'a',
type: 'unstyled',
text: 'hello '.repeat(1000),
characterList: Immutable.List(
Immutable.Repeat(CharacterMetadata.EMPTY, 6 * 1000),
),
});
};

const getSelection = () => {
return new SelectionState({
anchorKey: 'a',
Expand Down Expand Up @@ -454,15 +465,80 @@ test('must split styled spans apart within decorator', () => {
expect(el.children[1].type).toBe(DraftEditorLeaf);
});

test('must scroll the window if needed', () => {
test('must scroll the window if block is below viewport, block should be at bottom of viewport', () => {
const props = getProps(getHelloBlock());

getElementPosition.mockReturnValueOnce({
x: 0,
y: 800,
width: 500,
height: 16,
});
getElementPosition
//block
.mockReturnValueOnce({
x: 0,
y: 800,
width: 500,
height: 16,
})
//editor
.mockReturnValueOnce({
x: 0,
y: 0,
width: 500,
height: 800,
});

const container = document.createElement('div');
ReactDOM.render(<DraftEditorBlock {...props} />, container);

const scrollCalls = window.scrollTo.mock.calls;
expect(scrollCalls).toMatchSnapshot();
});

test('must scroll the window if block is above viewport, block should be at top of viewport', () => {
const props = getProps(getHelloBlock());

getElementPosition
//block
.mockReturnValueOnce({
x: 0,
y: -200,
width: 500,
height: 16,
})
//editor
.mockReturnValueOnce({
x: 0,
y: -200,
width: 500,
height: 800,
});
getScrollPosition.mockReturnValue({x: 0, y: 200});
getViewportDimensions.mockReturnValue({width: 1200, height: 800});

const container = document.createElement('div');
ReactDOM.render(<DraftEditorBlock {...props} />, container);

const scrollCalls = window.scrollTo.mock.calls;
expect(scrollCalls).toMatchSnapshot();
});

test('must scroll the window if huge block is below viewport, block should be at top of viewport', () => {
const props = getProps(getHugeBlock());

getElementPosition
//block
.mockReturnValueOnce({
x: 0,
y: 900,
width: 500,
height: 945,
})
//editor
.mockReturnValueOnce({
x: 0,
y: 0,
width: 500,
height: 800,
});
getScrollPosition.mockReturnValue({x: 0, y: 0});
getViewportDimensions.mockReturnValue({width: 1200, height: 800});

const container = document.createElement('div');
ReactDOM.render(<DraftEditorBlock {...props} />, container);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,16 @@ exports[`must render multiple leaf nodes 9`] = `true`;

exports[`must render multiple leaf nodes 10`] = `true`;

exports[`must scroll the window if needed 1`] = `
exports[`must scroll the window if block is above viewport, block should be at top of viewport 1`] = `
Array [
Array [
0,
0,
],
]
`;

exports[`must scroll the window if block is below viewport, block should be at bottom of viewport 1`] = `
Array [
Array [
0,
Expand All @@ -73,6 +82,15 @@ Array [
]
`;

exports[`must scroll the window if huge block is below viewport, block should be at top of viewport 1`] = `
Array [
Array [
0,
890,
],
]
`;

exports[`must split apart styled spans 1`] = `2`;

exports[`must split apart styled spans 2`] = `true`;
Expand Down