This is a feature idea for hidden blocks/inlines as a first-class citizen.
I'm working on folding behavior in my editor. So, in some instances, certain chunks of the document should not be visible.
However, I'm struggling to find a way to implement this. Since Slate operates on the visible content, if the blocks are styled to not be displayed and the user operates on the surrounding area, the blocks can be easily deleted from the document in error. Or, if I style them to be rendered but not visible (absolutely positioned off-screen, for example), they still steal the selection when moving between lines.
If I create a fragment of the blocks and store it elsewhere when folded, then insert it back in when unfolded, I mess with the operation history, which is not ideal for collaborative editing.
So, my suggestion (which I would be willing to look into implementation details and maybe even take a stab at it) is to support an isHidden property on nodes that allows a node to be included in the document, but not rendered. I'm not sure if this would mess with selection, but I'd imagine it could function somewhat like isVoid?
Another angle on it is that it's not Slate's concern to ignore rendering on bits of the document, since that's just presentation. Which, it may be desirable in some cases to have hidden element properties just be presentational and not part of the document data, eg. if the folded state wasn't actually a property of the document, but instead the individual user or device viewing the document (probably will be true in my case). The presentation would be determined by data external to the editor and document.
In that case, the ideal outcome would be that Slate could handle blocks or inlines with display: none and still treat them as part of the document (ie. not delete or replace them on splits and other adjacent operations), but ignore them for selection.
What would happen if you just set the content editable of all of those hidden blocks to false? and then with css made it so they couldn't be selected?
Hey @alanctkc, interesting problem and good write up!
For all of the solutions you present the problems do seem like they make it impossible to get right.
I think there might be one other user land solution which would be to use onChange (and maybe onBeforeChange to look at the operations that are there and if any of them effect the hidden nodes, to ignore them.
There might be a few pieces missing from core to make this solution super easy to do鈥攁llowing onChange to return a new change object entirely being one of them I think. But I think there's enough there to mock-up a hacky solution. And then we could add to core where it makes sense for cleaning it up.
Can you investigate that? Let me know what you find out!
I'd rather not have to add more logic that complicated how core handles changes themselves, since it gets convoluted fast.
@ianstormtaylor Thanks for the reply! Actually, that's the path I started down last night, and it got relatively complicated pretty quickly. That said, I'm not against complicated if I can close all the loops and it ends up being the most userland-ish.
Here are some things I ran into:
onBeforeChange to insert operations _before_ the ones already in the change? Is the operations property mutable, or is there some other API?@YurkaninRyan that's actually a super interesting idea messing with contenteditable, and one I hadn't thought about at all. Theoretically, I could style stuff off-screen and avoid it stealing the selection during movement. Copy and paste might be a little unexpected, but I'm not actually against having the folded blocks show up in pastes. Does Slate provide an API for contenteditable manipulation, or am I on my own trying to gain control of the DOM over React's binding? Sounds pretty hacky in the latter case.
@alanctkc can you give a visual example of a delete operation involving a fold? Not sure I totally understand what your desired outcome it.
Wellllllll, shoot. I did some testing to try to get a visual, and actually discovered Slate is doing all the right things under the hood for folding to work properly, with no accidentally destructive operations. Whenever performing operations around hidden blocks, they _are_ actually treated as if they were still in the document like visible blocks. The data is right there, and I somehow completely missed it.
My problem was in the way I referenced blocks for unfolding... the fold/unfold UI was on a sibling block of the hidden block, so if the sibling relationship changed during an operation, then I wasn't able to unfold the hidden block by referencing it as a sibling of the visible UI, and I mistook that for the hidden block being deleted (it's not).
So, all that to say, there are a few tweaks I need to make to preserve references to hidden blocks properly, and a couple edge case behaviors I'd like to modify, but Slate is totally doing the expected thing and isn't a part of the equation at all. In fact, I'm not building a code editor (Ace already got that one in the bag), but the folding behavior I have using Slate seems to already behave almost identical to folding in my code editor out of the box.
Thanks for the attention to this and sorry for the noise! Let me know if it's still not clear, but it seems like Slate already supports hidden nodes. :)
Haha awesome :smile: