Support for inline code blocks
cubuspl42 opened this issue · 11 comments
Introduction
Multiple existing apps (both native and web apps) have support for so-called inline code blocks. Just a few examples:
Slack
Discord
GitHub
Lorem ipsum dolor
lorem ipsum
Inline code blocks can span across multiple lines if necessary.
Details
It's difficult or impossible to directly achieve the inline code block effect in React Native.
As I see it, there are three things in React Native that block us from achieving the discussed effect easily.
The line gap
Unlike CSS on the Web, if the inline text has a background color set, it fills the whole line. In CSS, it tightly wraps the inline text.
CSS:
React Native:
What's interesting, it seems there's no straightforward way to achieve the first effect on the second platform and vice-versa.
Such a feature exists in another W3 standard, TTML, and is named fillLineGap
. It was added in 2017.
Figure 1 Illustrative rendition of the example immediately above with itts:fillLineGap="true" removed (left) or preserved (right). Blue lines have been added to show the before-edge and after-edge of each line area, which are coincident for successive line areas.
Also in 2017, there was some discussion about adding such a feature to CSS.
Doing just the inline-padding isn't the only place we want to do something with line areas.
... Another feature, fill-line-gap, says "draw background areas between lines".
It doesn't seem like much was done on this topic after that.
CSS behaves as if it had implicit fill-line-gap: false
, and React Native behaves as if it had fill-line-gap: true
.
Inline padding
In CSS, inline elements can have padding.
In React Native, the padding
style property is ignored for inline text. As the current behavior is to fill the whole line height, it's not clear how padding should behave (or if it should be honored at all).
Borders
In CSS, inline elements can have borders, also with rounded corners.
The exact behavior is controlled by the box-decoration-break property.
In React Native, border styles for inline text are ignored.
Discussion points
I'd like to discuss what's necessary to support inline code blocks in React Native.
- Is adding a boolean TTML-like
fillLineGap
style property to React Native reasonable?- The default value could be
true
(fill the whole gap), for backward compatibility - The
false
value would mean CSS-like behavior
- The default value could be
- If we supported
fillLineGap
, do we see any blockers from implementing these two inline text styles:- Padding
- Borders
What do you think?
@NickGerleman What do you think about this? I took much effort to sketch this idea in a clear way, also ensuring it fits well into the existing React Native ecosystem. I hope this proposal can be considered serious and achievable.
I'm tagging you because we already had an opportunity to work on the React Native Text
subsystem and I really appreciate all the feedback you gave me then. If you don't have time right now to look at this, it would be awesome if you gave some hints on how can I get some eyes on this proposal.
I think a prop like fillLineGap
is reasonable, and extra CSS properties on nested text make sense. The most significant challenge may be that we are constrained by what is allowed by the platforms text drawing system.
Right now, these are flattened into a single stream of rich text characters drawn by the platform's underlying text system. On Android, you do get a Paint
instance for text spans, where you can do some pretty custom things.
RN does allow views inline in text as well. This is akin to an element formatted with inline-flex
, and any view styling can be added to it. But this will not line-break, and acts differently from the goal here.
I managed to prototype this feature on both iOS and Android. It's a proof-of-concept of respective platforms' possibilities, in tiny Swift/Kotlin native apps.
Both platforms resisted, but it was possible to achieve the desired effect on both of them in a surprisingly analogous manner.
To support left/right padding, it was necessary to inject artificial characters into the NSAttributedString
/ Spannable
and later style at appropriate places to actually give them the desired bounding box (width). It's nearly for sure necessary on iOS and might be necessary on Android.
To support spanning across multiple lines, on both platforms, it was necessary to override some new (from React Native's perspective) APIs. On iOS it was necessary to subclass NSLayoutManager
and NSLayoutManagerDelegate
, on Android it was necessary to subclass TextView.onDraw
.
@NickGerleman What's the next step here? Should I file a PR with a formal API proposal? Should I file a separate one for each new feature, or one joint one, or maybe only fillLineGap
requires a proposal PR and the others can be considered "adding missing functionality"?
Please feel free to make PRs for these, preferably as granular as possible. I have context to review the Android implementation, and can try to flag someone to look at the iOS one.
@NickGerleman I was asking about feature proposal PRs in this repo (React Native: Discussions and Proposals), did you also mean that? So far this is an issue.
The first PR is slowly getting closer to being merged (🎉), but it's not the end of our quest. It's just the beginning.
I want to start organizing the next steps.
First topic I would like to raise is modelling the new styles on the "core" (ReactCommon
) level.
Currently, we have AttributedString
, modeling a list of text fragments with denormalized style:
As I see it, what's necessary is to adjust it to a list of spans, which would hold styles like background color, border and padding. The rest of text styles could be kept in fragments. A span would contain a list of fragments.
This assumes we don't want to support span nesting, at least initially.
What do you think about this? Is it a reasonable model?