Slate: Allow Selection on Right Edge of Inlines

Created on 17 Mar 2019  路  8Comments  路  Source: ianstormtaylor/slate

Do you want to request a _feature_ or report a _bug_?

Feature (or bug report if you consider bad assumptions a bug).

What's the current behavior?

Currently, Slate does not allow for the selection to be on the right edge of an inline node. If it detects that it is, it "normalizes" such that the caret is in the leftmost position (zero offset position) of the subsequent text node.
https://github.com/ianstormtaylor/slate/blob/3dce916074e0294a5045f9c47f6932ac5841e947/packages/slate-react/src/utils/set-selection-from-dom.js#L46-L57

What's the expected behavior?

The comment says

If the selection is at the end of a non-void inline node, and there is a node after it, put it in the node after instead. This standardizes the behavior, since it's indistinguishable to the user.

which is not the case (at least, not always).

In particular, this makes it impossible to implement "sticky" inlines or programatically.
My particular use case for this is embedding LaTeX into a slate document.
It's important to know when you're appending LaTeX markup and when you're appending "normal" text; in particular, we implement something where if the math is selected, then it's rendered as plain text, but if the selection is elsewhere, it's rendered as rich math.

Mynerva - Edited

As you can see above, it's impossible to append anything to the inline (which I imagine is actually a pretty popular thing to do!) and the only "workaround" is to add your content before the last character and then delete the last character.

The slate-sticky-inlines plugin does not help (the master of that project is currently using an old version of Slate, but @gnestor upgraded it - but it still doesn't work using the latest Slate for the reasons outlined above).

Resolution

I'm not sure what the expected resolution is, but I hope you'll try to support this use case. At the very least, maybe not running that function after programatically setting the selection would be a good idea (or some kind of flag to turn it off)?

I'd be happy to write a PR if you have ideas about the "best way" to do this.

Most helpful comment

Another note for posterity:
https://github.com/YurkaninRyan/slate-sticky-inlines/issues/5#issuecomment-456508573

Note: For 0.42 as the comment said, but this should be easy to adapt for 0.44 and beyond

    onSelect(event, change, next){
        var range = SlateReact.findRange(window.getSelection(), change.editor);
        if (!range) return;
        var focus = range.focus;
        var document = change.editor.value.document;
        var focusText = document.getNode(focus.key);
        var focusInline = document.getClosestInline(focus.key);
        if (focusInline && !change.isVoid(focusInline) && focusInline && range.isCollapsed && focus.offset === focusText.text.length) {
            var selection = document.createSelection(range);
            selection = selection.setIsFocused(true);
            change.select(selection);
        } else next();
    }

All 8 comments

This has been brought up before, please search issues for prior discussions for more information if you intend to tackle it.

As for other cases, I鈥檓 open to it. But it will require someone championing it and doing the work to make it well thought out. I鈥檓 not sure that there is a better default than we currently have. But opening up the restriction alone could be an option.

@travigd went source diving. If I'm not mistaken the function above seems to only be called onInput and onSelect , which implies to me that this is just a matter of overriding these events with your own plugin.

https://docs.slatejs.org/slate-react/plugins#onselect

Unfortunately there doesn't seem to be an exposed onInput hook for plugins right now. But IIRC those are pretty easy to add. It sounds like what you really need is for the selection to not be mucked with during the onSelect event though so that might be sufficient?

Worth a try probably

On further research, I'm pretty confident that all that slate-sticky-inlines needs to be functional again is to figure out the correct override to onSelect to allow the selection to sit on the end edge of inlines that are supposed to be sticky. I think we should move this issue over there, though it might need to have a permanent fork and might not be a fruitful enterprise.

What are your thoughts?

(Posting for posterity)

For my own personal use case, I was able to hack around the limitation by enforcing an extra space at the end of the text of the inline. This required...

  • A schema rule that enforces a space character at the end of a text (as well as a rule that says there's exactly one text child)
  • Custom behavior to handle backspace (in my implementation, backspacing into a latex inline like above just moves the selection into the node, it doesn't actually delete anything).
  • CSS hacks to avoid displaying the extra space in "editing" mode (margin-right: -1ch; worked).

@CameronAckermanSEL If that route is elected, it'd be nice to export the utility functions that it uses and add an option to normalize the selection position or not (to avoid copying and pasting a lot of the code with a single change).

Another note for posterity:
https://github.com/YurkaninRyan/slate-sticky-inlines/issues/5#issuecomment-456508573

Note: For 0.42 as the comment said, but this should be easy to adapt for 0.44 and beyond

    onSelect(event, change, next){
        var range = SlateReact.findRange(window.getSelection(), change.editor);
        if (!range) return;
        var focus = range.focus;
        var document = change.editor.value.document;
        var focusText = document.getNode(focus.key);
        var focusInline = document.getClosestInline(focus.key);
        if (focusInline && !change.isVoid(focusInline) && focusInline && range.isCollapsed && focus.offset === focusText.text.length) {
            var selection = document.createSelection(range);
            selection = selection.setIsFocused(true);
            change.select(selection);
        } else next();
    }

One of the options is to keep zero width character at the beginning and ending of inline. Then write necessary editing (backspace, delete, arrow movement, ...) plug-ins to make sure that zero space character stays in place and to have the expected functionality.

I've been asked about my workaround, so I'll link to the gist here:
https://gist.github.com/travigd/ae63368d8c646f6fadbb1cea53227884

It was written a few minor versions of Slate ago but it should be more-or-less the same.

I believe this is currently because of this bug, so I'm going to close in favor of: https://github.com/ianstormtaylor/slate/issues/3148

Was this page helpful?
0 / 5 - 0 ratings