New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Tab support #4436
Tab support #4436
Conversation
The latest updates on your projects. Learn more about Vercel for Git ↗︎
|
size-limit report 📦
|
@zurfyx I just want to tell you, this is amazing.
Just a couple thoughts. Again, really impressed that you dug through this. It's not an easy area, especially as navigation keeps stealing the spotlight from tabs. Confronting this in Lexical has turned me into an unabashed tab defender. lol. -j |
@abelsj60 Thank you, appreciate the warm comment! Your PR was great, not only it fixed the immediate issue but also helped me find the root cause. It is part of the process. I also understand that this one does require a big block of time, as it's still not yet ready to merge.
cc'ed him on Discord, thanks for the heads up! I believe this PR covers it but otherwise we can discuss further.
Yes, via theming. There's a TODO I need to address since it's currently hardcoded for CodeTabNode but otherwise it should be customizable. Let me know if you recall this special case, I might have missed something. |
@zurfyx I took a quick look. I guess spacing is less a special case than a manifestation of the results people expect from a tab character. Their idea is bound to be custom. If it's helpful, I'll toss out my questions from the code:
I know you have it all in hand, this is just what pops to my mind. Love the use of the Override API. Full customization... -j |
@abelsj60 - Appreciate the in-depth thinking process!
Easier than that, you can just add
Yes, because they behave differently, the TabNode resizes as you type (unless it's size 1), while CodeTabNode is fixed size. Also, while you would usually type JS with 2 spaces, Java is often 4 so that's a valid reason to allow people customize this.
Yes! Added some more details on the description above. Let me know the above doesn't cover this question.
Correct
Yes
Adds another tab (i.e. GDoc's behavior) |
... This is great, @zurfyx. I come from the school of "speak now or forever hold your peace," so try to get it all out. Your enhanced description at the top is helpful. I'd be afraid that This is exciting. Tabs without hand-wringing. Pretty cool... -j |
@abelsj60 - Ah, yes, that's correct. I should've clarified that |
@zurfyx (RawLasagna here) noticed something strange, when I tab at the end of node after writing (this behaviour is not very consistent though). Is this intended? zurfyx.tab3.mov |
@rferreira98 - Thanks for the feedback! I think so, besides we don't do any special rendering of the tab in this case, I can repro the same behavior in GDocs, MS Word and Apple Notes. |
@zurfyx The shift tab doesn't work if the contents are not tab in the beginning. Try in playground to tab and shift tab. It doesn't outdent. |
Tab
This PR implements Tab (
\t
) support.intro2.mov
The camouflaged broken experience
Turns out that this basic feature was never implemented. My hunch is that we thought it would work out of the box on a
contenteditable
but the reality is very different. Particularly, for Chrome.Previous behavior (all it takes is to paste a Tab or type one in a code block (that can bubble up to the parent)):
Screen.Recording.2023-04-30.at.8.13.33.pm.mov
It wasn't until very recently, that @abelsj60 took a stab at it (#3770) to fix a pain point with CodeNodes (gated via Node props), but after the internal report (#4399) and the fact that we don't necessarily want to override the default CodeNode, it revealed a major issue.
It is a non-obvious one because of the current indent behavior (everything's an indent) (similar to Quip), that forces you to indent the content wherever you are, preventing you from tabbing the content.
I believe the ideal behavior is somewhere closer to Google Docs or MS Word, where you can have a mix of both (and support for customizability). You can indent when the selection spans across multiple blocks or at the beginning but otherwise you'll tab (this is a simplication, see unit tests).
Chrome
Interestingly, this only applies to Chrome, but the fact that everyone uses Chrome makes it a no-brainer to write a fix for them. As it turns out, Chrome will break your span when it contains a
\t
. The DOM Mutations will return a valid state but it is far from a Lexical valid state and with what we have now, Lexical will just drop it.Screen.Recording.2023-04-30.at.6.47.47.pm.mov
You can repro this here. https://codepen.io/zurfyx/pen/bGmrrpX
Failed attempts
First attempt (#4434)
Expand the Rich Text plugin approach to make tabs also work with controlled mode. This way, we can dodge the complicated Mutations and still perform quite optimally. Won't quite work as Chrome will still destroy your span later even if you're not anywhere close to the tab. F
Second attempt (#4435)
Try reconstruct a valid state from the Mutations. This won't quite work for IME because as you soon as you touch selection you kill it. F
The solution that works (this PR)
Turns out that Chrome will not destroy anything but the contents of a
span
. I kinda realized that via Prosemirror that doesn't usespan
wrapper and they don't have this issue on their basic example editor. You can combine\t
just fine on ap
without any special logic.Unfortunately, Lexical relies heavily on
span
so we can't just drop them. However, we can isolate it. We can do this via the newly introduced LexicalTabNode, aspan
that renders just\t
.This also works well with IME as it leaves the rest of the editor untouched!
Then, most of the PR goes around trying to make this Node on various cases.
The fastest approach would have been to have a
registerNodeTransform
, but it comes at the cost of performance, repeatedindexOf
andsplits
, so instead this PR handles every single case manually:Code
code.mov
So there's two type of tabs UX-wise. The nicely expansible tabs and the hardcoded-width tabs that are the commonly used in code.
For this reason, we need to introduce yet another tab node: CodeTabNode.
This tab will just simply render a space but the size is variable, just like in every editor.
Two particularly interesting points behind the implementation:
letter-spacing
is the only one that works across browsers and mobile.word-spacing
only works properly on Chrome and Firefox andwhite-space: pre-line
behaves badly when tab is at end.The rest of the PR just makes this work around indent/outdent and line-shifting.
Copy-paste
Most of the failing tests are about copy-paste. I intentionally didn't fix them yet because I don't understand the GDoc fixes in #2969. By doing this we broke a valid copy-paste case, demostrated in https://codepen.io/zurfyx/pen/KKGvvNV
Will need your help @fantactuka / @acywatson
Peers
References
Fixes #4429
Fixes #4399
Fixes #4433
TODO