jpuri / react-draft-wysiwyg

A Wysiwyg editor build on top of ReactJS and DraftJS. https://jpuri.github.io/react-draft-wysiwyg
MIT License
6.41k stars 1.16k forks source link

Autoscroll on new line #964

Open fihis opened 4 years ago

fihis commented 4 years ago

How can I make editor's input to scroll down automatically on new line input? Like in example 6 at https://jpuri.github.io/react-draft-wysiwyg/#/demo. Now scroll stays on top and text cursor hides when goes below bottom border.

saeedkargosha commented 4 years ago

I have the same problem :(

maxleon52 commented 3 years ago

I have the same problem :(

Momomash commented 3 years ago

Who solved this problem?

vpaladino778 commented 3 years ago

Has anyone solved this issue?

When I press enter, it will start typing on a new line that is hidden, but once i type 2 characters, it suddenly jumps down so the text is visible again. Very odd behavior.

Momomash commented 3 years ago

Has anyone solved this issue?

When I press enter, it will start typing on a new line that is hidden, but once i type 2 characters, it suddenly jumps down so the text is visible again. Very odd behavior.

it seems not

jtrost commented 3 years ago

I solved this problem by attaching some hooks to the editor, and scrolling to the bottom of the div when the number of lines change.

  const [lines, setLines] = useState(0);
  const editorRef = useRef(null);

  const handleEditorChange = function(e) {
    const prevLines = lines;

    if(prevLines !== e.blocks.length) {
      setLines(e.blocks.length);
    }
  };

  useEffect(() => {
    const scrollDiv = editorRef.current.querySelector(".wysiwyg-editor");
    scrollDiv.scrollTop = scrollDiv.scrollHeight;
  }, [lines]);

return(<div className="row" ref={editorRef}><Editor onChange={(e) => handleEditorChange(e)} {...props} /></div>);
lomeat commented 3 years ago

@jtrost It is interesting decision. So i tried your variant. But class '.wysiwyg-editor' is not exist. I have only these: image I tried also '.rdw-editor-wrapper' and '.DraftEditor-root' and others. But all they have EditorState with _immutable state. So i can't figure how to scroll down.

bikashsahucbnits commented 3 years ago

hi i am working on react native and used react-draft-wysiwyg for web version of my app and i amfacing the issue of autoscrolling on new line when i hit enter on the in bottom of editor the cursor moved to the the new line but scroll is moving down ward i have tried lot for this all the solution i saw on the internet @jpuri please help me out from this

lomeat commented 3 years ago

@bikashsahucbnits you just now repeated the same about this thread, man.

sam-mfb commented 2 years ago

Here's a partial solution. It solves the main case--no scrolling at the end of the text when you are at the bottom of the view. You can still trigger this behavior if you are at the bottom of the view but still in the middle of the scroll. Anyway, in case it's helpful--

const editorRef = useRef<null | HTMLDivElement>(null)
  const bottomOffset = useRef("")
  const editorContainingDivClass = ".public-DraftEditor-content"
  const scrollIfNewElementAtEnd = () => {
    if (editorRef.current) {
      const scrollDiv = editorRef.current.querySelector(
        editorContainingDivClass
      )
      if (scrollDiv) {
        const newElement = scrollDiv.lastChild?.lastChild as HTMLElement
        const currentBottom = newElement.dataset.offsetKey
        if (
         currentBottom !== "" &&
          currentBottom !== bottomOffset.current
        ) {
          bottomOffset.current = currentBottom ?? ""
          newElement.scrollIntoView()
        }
      }
    }
  }
Sufflavus commented 2 years ago

Here is a solution that works for me.

Packages that I use

"draft-js": "^0.11.7",
"react-draft-wysiwyg": "^1.14.7",

My styles for scrolling

.DraftEditor-root {
    height: 200px;
    overflow-y: auto;
}

.DraftEditor-editorContainer,
.public-DraftEditor-content {
    height: 100%;
}

Some extracts from my .tsx file:

import { Editor } from 'react-draft-wysiwyg';
import { ContentState, EditorState, SelectionState } from 'draft-js';

Save ref for future usage

_editorReferece: any = null;

_setEditorReference = (ref: any) => {
    if (!ref || !!this._editorReferece) {
        return;
    }

    this._editorReferece = ref;        
};

OnChange event handler

_onEditorStateChange = (editorState: EditorState) => {
    const   
        currentContentState: ContentState = this.state.editorState.getCurrentContent(),
        newContentState: ContentState = editorState.getCurrentContent(),

        lastChangeType: string = editorState.getLastChangeType(),
        enterWasClicked = currentContentState !== newContentState && lastChangeType === 'split-block';

        this.setState({ editorState }, () => {
            // It is important to adjust scroll position after state was set
            if (enterWasClicked) {
                this._scrollToCursor();
            }
        });
};

_scrollToCursor = () => {
    if (!this._editorReferece) {
        return;
    }

    // Find focused element using SelectionState
    // Find out where focused element is located inside container
    // If focused element is not visible, change position of the scroll

    const
        { editorState } = this.state,
        selectionState: SelectionState = editorState.getSelection(),
        focusedBlockKey = selectionState.getStartKey(),

        rootElement: HTMLElement = this._editorReferece, // element with class public-DraftEditor-content
        scrollableContainer = rootElement!.parentElement!.parentElement as HTMLElement, // element with class DraftEditor-root

        blocksWrapper = rootElement.lastChild as HTMLElement, // direct child of the element with class public-DraftEditor-content
        blocks = [...blocksWrapper.children] as HTMLElement[],

        focusedBlockIndex: number = blocks.findIndex((element: any) => element.dataset.offsetKey.startsWith(focusedBlockKey)),
        focusedBlock: HTMLElement = blocks[focusedBlockIndex],

        blockLineHeight = 20, // depends on block type, so has to be calculated
        focusedBlockFirstLineBottom = focusedBlock.offsetTop + blockLineHeight,
        maxVisiblePosition = scrollableContainer.scrollTop + scrollableContainer.clientHeight,

        diff = focusedBlockFirstLineBottom - maxVisiblePosition;

        if (diff > 0) {
            scrollableContainer.scrollTop += diff + 5;
        }
};
<Editor
    editorState={this.state.editorState}                    
    editorRef={this._setEditorReference}                    
    onEditorStateChange={this._onEditorStateChange}
/>
MamorukunBE commented 2 years ago

For an unmanaged component, I did find a way to mostly solve the problem (the Editor correctly scroll down at a new last line, but still struggles autoscrolling when a new line added within the text overflows the editor <- to analyse). This is a pure empirical code, so please don't hesitate to correct it if you see any flaw :)

  // The code bellow is to be added into the class/function rendering the <Editor />

  const [recordedLastLineContent, seRecordedtLastLineContent] = useState(null);

  let handleEditorChange = (editorState) => {    // To pass to the <Editor /> as its onChange callback
    let lastLineContent = editorState.getCurrentContent().blockMap.last();
    if (recordedLastLineContent && lastLineContent !== recordedLastLineContent) {    // Detects that a new line has been added
      seRecordedtLastLineContent(lastLineContent);
      const scrollableDiv = editorRef.parentNode.parentNode.parentNode;    // editorRef: returned by editorRef callback of the <Editor />
      setTimeout(() => { scrollableDiv.scrollTop = scrollableDiv.scrollHeight }, 20);    // Give 20ms to the <Editor /> to update its own content before scrolling down
    }
    seRecordedtLastLineContent(lastLineContent);
  }
Borislav-ARC commented 2 years ago

Still have this problem

rakibsxyz commented 1 year ago

have you solved this problem? stuck on it

biancadragomir commented 1 year ago

did anyone find a solution?

kassiogluten commented 1 year ago

FINNALY solved it, just move ur dynamic import to ouside of all ur components

manishvvasaniya commented 1 year ago

FINNALY solved it, just move ur dynamic import to ouside of all ur components

That's great, can you please elaborate more ?

kassiogluten commented 1 year ago

FINNALY solved it, just move ur dynamic import to ouside of all ur components

That's great, can you please elaborate more ?

I was using next dynamic import inside the component like this:

import dynamic from "next/dynamic";

export function Prontuario({ notes, setNotes }) {
  const Editor = dynamic(
    () => import("react-draft-wysiwyg").then((mod) => mod.Editor),
    { ssr: false }
  );
  return (
    <Editor
      editorState={notes}
      onEditorStateChange={setNotes}
      // toolbarOnFocus
      localization={{
        locale: "pt",
      }}
      toolbar={{
        options: ["inline", "list", "textAlign", "remove", "history"],
      }}
    />
  );
}

So a move up one line this code:

 const Editor = dynamic(
    () => import("react-draft-wysiwyg").then((mod) => mod.Editor),
    { ssr: false }
  );

Ended up like this:

import dynamic from "next/dynamic";

const Editor = dynamic(
  () => import("react-draft-wysiwyg").then((mod) => mod.Editor),
  { ssr: false }
);

export function Prontuario({ notes, setNotes }) {
  return (
    <Editor
      editorState={notes}
      onEditorStateChange={setNotes}
      // toolbarOnFocus
      localization={{
        locale: "pt",
      }}
      toolbar={{
        options: ["inline", "list", "textAlign", "remove", "history"],
      }}
    />
  );
}
vanHuong0202 commented 1 year ago

Did anyone find out a solution for this issue?

vanHuong0202 commented 1 year ago

Finally found out the solution for this issue,add this logic to Editor: onContentStateChange={() => { if (editorRef.current) { const scrollDiv = editorRef.current if (scrollDiv) { const newElement = scrollDiv.lastChild ?.lastChild as HTMLElement newElement?.scrollIntoView() } } }}

drn77 commented 1 year ago

I did it like that: useEffect:

useEffect(() => {
    const childNode = editorRef?.current?.wrapper?.parentElement?.childNodes[1];
    const lineHeight = childNode?.children[0]?.children[0]?.children[0]?.children[0]?.lastChild?.clientHeight;
    if (
        childNode?.scrollHeight > 0 &&
                childNode?.scrollHeight - childNode?.offsetHeight == childNode?.scrollTop + lineHeight
    )
        childNode?.scrollTo({ top: childNode?.scrollHeight - childNode?.offsetHeight });
    }, [editorRef.current?.wrapper?.scrollHeight]);

Editor component

<Editor ref={editorRef} />

Hope it will be usefull. When you are adding a new line and the scroll is on the bottom of the container then it will scroll down, but if you are somewhere in the midle of the container then it won't. I think it should work like that.

rsys-suraj-kumar commented 11 months ago

Hey This Solution Works for me !!! Try this out

onContentStateChange={() => { const contentDiv = document.querySelector(".rdw-editor-main"); console.log(contentDiv.scrollHeight); contentDiv.scrollTop = contentDiv.scrollHeight; }}

thiwanka-wickramage commented 7 months ago

Finally I found a solution with css. I just override the DraftEditor-editorContainer class.

.DraftEditor-editorContainer {
  height: 200px; // my text editor height
  overflow: auto;
  padding: 0px 14px;
}

Recording 2024-02-01 at 16 11 37