Coder Social home page Coder Social logo

Comments (22)

AlastairTaft avatar AlastairTaft commented on April 20, 2024 16

Here's my function for getting the selected block element

getSelectedBlockElement = () => {
  var selection = window.getSelection()
  if (selection.rangeCount == 0) return null
  var node = selection.getRangeAt(0).startContainer
  do {
    if (node.getAttribute && node.getAttribute('data-block') == 'true')
      return node
    node = node.parentNode
  } while (node != null)
  return null
};

from draft-js.

hellendag avatar hellendag commented on April 20, 2024 7

Great question.

For the Notes editor, I reach directly into the DOM Selection object, identify the block that contains the cursor, and calculate the position of the element.

This isn't exposed in the current API, though I think it might be useful to have a utility function available so that this doesn't need to be reimplemented.

from draft-js.

gscottolson avatar gscottolson commented on April 20, 2024 5

A utility that provides a reference to the current block with cursor focus would be great.

How about the boundingClientRect of the current selection (to ease in positioning popups, widgets, etc)?

from draft-js.

bkniffler avatar bkniffler commented on April 20, 2024 5

I've had some success with https://github.com/bkniffler/draft-wysiwyg/blob/master/src/draft.js#L67 and https://github.com/bkniffler/draft-wysiwyg/blob/master/src/draft.js#L269

Its a mouseUp handler on the draft-js wrapping div.

mouseUp(e) {
      function getSelected() {
         var t = '';
         if (window.getSelection) {
            t = window.getSelection();
         } else if (document.getSelection) {
            t = document.getSelection();
         } else if (document.selection) {
            t = document.selection.createRange().text;
         }
         return t;
      }

      setTimeout(()=> {
         var selection = this.state.value.getSelection();
         if (selection.isCollapsed()) {
            return this.setState({toolbox: null});
         }
         else {
            var selected = getSelected();
            var rect = selected.getRangeAt(0).getBoundingClientRect();
            this.setState({toolbox: {left: rect.left, top: rect.top, width: rect.width}});
         }
      }, 1)
   }

The resulting position data can easily be used with something like react-portal to show a tooltip like here https://github.com/bkniffler/draft-wysiwyg/blob/master/src/components/tooltip.js

The live example: http://draft-wysiwyg.herokuapp.com/
I've only tested in Safari/Chrome though.

from draft-js.

gscottolson avatar gscottolson commented on April 20, 2024 5

@AlastairTaft Getting the current block is straightforward using Draft:

const currentContent = editorState.getCurrentContent()
const selection = editorState.getSelection()
const currentBlock = currentContent.getBlockForKey(selection.getStartKey())

I prefer this technique over using the window object and digging through DOM nodes.

from draft-js.

iamsaso avatar iamsaso commented on April 20, 2024 4

One solution would be to add ref={blockKey} to DraftEditorBlock or its parent div so we could use findDOMNode to get the element.

Example:
ReactDOM.findDOMNode(editorState.getSelection().getStartKey())

from draft-js.

hellendag avatar hellendag commented on April 20, 2024 4

What would be most helpful here? A utility to provide the DOM element for the selected ContentBlock? I don't want people to have to go digging into the Selection API docs at MDN. :) So I can put something together if it would be useful.

from draft-js.

jmaguirrei avatar jmaguirrei commented on April 20, 2024 3

@azangru This is working for me:

// From MobX observables

  @observable isSelectionActive = false;
  @observable selectionCoords = {};

  @action modifySelection(value) {
    this.isSelectionActive = value;
  }

  @action setCoords({ editorBound, cursorBound }) {
    console.log('editorBound, cursorBound', editorBound, cursorBound);
    this.selectionCoords = {
      top: cursorBound.top - editorBound.top + 20,
      left: cursorBound.left - editorBound.left,
    };
  }

// The events of the class

  _handleBeforeInput(char) {

    switch(char) {

      case '@': {

        // The rectangle that bounds the editor
        const
          editorNode = document.getElementById(this.props.id),
          editorBound = editorNode.getBoundingClientRect();

        // The rectangle that bounds the cursor
        const
          s = window.getSelection(),
          oRange = s.getRangeAt(0),
          cursorBound = oRange.getBoundingClientRect();

        this.setCoords({ editorBound, cursorBound });
        this.modifySelection(true);
        return true;
      }

      case '#': {
        console.log('handled #!');
        break;
      }

    }

    return false;

  }

What is still needed is to control when the position of the caret is close to the edge to prevent the autosuggest div to overflow the parent div. In that case the property should be top & right instead of top & left.

Hope this helps!

from draft-js.

arstin avatar arstin commented on April 20, 2024 2

Great library! I was toying around with the Medium style formatting bar shown in the video (centering controls above selection), and, from quickly digging around MDN got something working in Chrome with just window.getSelection().getRangeAt(0).getBoundingClientRect(). But I have no clue if this will break in IE, etc.

It'd be amazing if this library itself could handle any cross-browser and edge cases involved in positioning relative to a selection!

from draft-js.

hellendag avatar hellendag commented on April 20, 2024 2

For retrieving information about the ContentBlock that contains selection, definitely rely on the model, not on the DOM.

For retrieving information about the actual node in order to obtain DOM position information, @AlastairTaft's solution is pretty close to what we use internally. :)

from draft-js.

hellendag avatar hellendag commented on April 20, 2024 2

Hopefully this will be useful as well: https://github.com/facebook/draft-js/blob/master/src/component/selection/getVisibleSelectionRect.js

Going to go ahead and close this issue since we've got a useful thread here.

from draft-js.

tleunen avatar tleunen commented on April 20, 2024 2

I'm trying to display a suggestions container and I'm trying to use the "new" getVisibleSelectionRect, but it returns null when the editor is empty. And the editor is empty when I'm typing the first character, because I'm using the function inside the onChange event.
So I tried to use @AlastairTaft's code to get the block and calculate the position but I'm having some difficulties to properly get the exact position of the cursor/selection, because the block is quite large. I cannot display the container at the beginning/end of the block. @hellendag do you mind sharing a bit how you do this at facebook?

from draft-js.

azangru avatar azangru commented on April 20, 2024 1

@jmaguirrei Wow, thank you!

I eventually adopted the approach taken by the Draft-js-plugins team, where they use a draft-js decorator to wrap the word for which autosuggestion is offered in a span, and save the coordinates of that span. Once those coordinates are available, it is trivial to position the autocompletion box near the word that the cursor is in.

Thank you for offering another solution to this problem!

from draft-js.

joergrech avatar joergrech commented on April 20, 2024 1

Using window.getSelection().getRangeAt(0).getBoundingClientRect() or document.getSelection().getRangeAt(0).getBoundingClientRect() to get the "cursor's" position does not work with Safari 10.0.3. It works with Chrome, Firefox and Edge, though.

Any idea how to get the position in another way?

from draft-js.

bkniffler avatar bkniffler commented on April 20, 2024

Yes, I've wondered the same. Is there some kind of event on editor that gets fired if the selection changed with infos about position etc.?

from draft-js.

hellendag avatar hellendag commented on April 20, 2024

Each top-level block element has data-block={true}, which you can use to find it if looking upward from the node with the current DOM Selection.

from draft-js.

iamsaso avatar iamsaso commented on April 20, 2024

Thank you that helps a lot!

from draft-js.

gscottolson avatar gscottolson commented on April 20, 2024

@bkniffler Nice! Thanks for sharing.

from draft-js.

AlastairTaft avatar AlastairTaft commented on April 20, 2024

I managed to get this working. To position the buttons on the left, I first retrieved the selected block element using something akin to the function above. Then I used the offsetTop property as it's parent node is the editor container, so it's safe to use this. This gave me the vertical offset for the buttons.

To then display the popover container which adds all the inline styles, like bold, italic, link, etc. I grabbed the position of the editor and the position of the current selection using the getBoundingClientRect method and computed the difference between the two to figure out where to place the inline buttons relative to the editor.

See it in action here

from draft-js.

AlastairTaft avatar AlastairTaft commented on April 20, 2024

@gscottolson That's much nicer thanks

EDIT: Just realised that method pulls back the object representation of the block not the DOM element.

from draft-js.

azangru avatar azangru commented on April 20, 2024

Reading through this thread several months after it was closed, and still am confused. I too would like to display a popover block with autocomplete suggestions positioned close to the caret. Is there a good technique for finding the position of the caret (its absolute top and left offsets or the offsets relative to the Editor element)?

from draft-js.

jmaguirrei avatar jmaguirrei commented on April 20, 2024

@azangru You are welcome, glad to help! I havent finished my editor component yet, so maybe I will be trying another solutions, like yours, in the future. Keep in touch. Regards

from draft-js.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.