Repository navigation
feat: add a per-block line wrap toggle to code block headers - #1021
Merged
Merged
Conversation
Adds an opt-in `enableCodeBlockWrapToggle` prop to the React Native package. Each top-level fenced code block gets a wrap button next to copy; tapping it wraps that block's lines at the block width, and tapping again returns to horizontal scrolling. Wrap state is a set of document-order indexes of top-level code blocks, fed to the view-free measure pass (part of the iOS measurement cache key and the Android measure hash). It resets when anything before the previous document's last line changes, so streaming and task toggles keep it. Labels come from `accessibilityLabels.codeBlock.wrap` / `.unwrap`. Closes #1009 Co-authored-by: Julius Marminge <julius0216@outlook.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…kdown toggleTaskListItemAtIndex wrote `[ ]` for checked and `[x]` for unchecked, the inverse of Android, since #92. The checkbox looked right because the attributed string is patched separately, but the cached markdown recorded the opposite state, so anything reading it after a tap saw a stale checkbox and a prop echo of the toggled document never matched the local render. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…icon iOS reset the wrapped set when the markdown prop changed before its last line, which a task-toggle echo always does. The reset now skips a prop that equals the markdown already rendered locally, so the reader's choice stands. Android drew the wrap glyph through androidx.core.graphics.PathParser, a RestrictTo API, as a filled shape. WrapGlyph strokes the same Material outline with the copy glyph's paint, so the two header buttons match. Docs note that macOS accepts the prop without a toggle. Comments restating adjacent code are trimmed. CodeBlockContainerViewTest moves to a follow-up PR that adds native coverage on both platforms. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
3 of 6 tasks
eszlamczyk
force-pushed
the
feat/code-block-wrap-toggle
branch
from
October 16, 2026 20:44
0caad5c to
dda187b
Compare
Collaborator
Author
|
Verified everything via tests and manual checks, on both platforms:
Via a throwaway screen |
eszlamczyk
commented
Oct 16, 2026
Collaborator
Author
There was a problem hiding this comment.
Unrelated change to the pr, fixes the bug that made screens crash on changing the text size on the fly
hryhoriiK97
approved these changes
Oct 16, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What/Why?
Closes #1009.
In the React Native package, a top-level fenced code block scrolls horizontally. On a phone, a long shell command or a one-line JSON blob means swiping back and forth inside a small box. Wrapping every block instead makes indented code harder to read. This lets the reader choose per block.
The new
enableCodeBlockWrapToggleprop (defaulttrue; setfalseto hide it) adds a wrap button to the left of copy in each fenced code block header (flavor="github"), including blocks inside blockquotes and admonitions:text.alignleftSF Symbol. Wrapping limits the cached TextKit container to the pane width and turns off horizontal scrolling.wrap_textglyph. Wrapping caps the codeTextView's width at the pane width inside the existingHorizontalScrollView.accessibilityLabels.codeBlock.wrap/.unwrap, with defaults "Wrap lines" / "Disable line wrap".MeasurementCacheKey. On Android it goes into the measure hash.The standalone packages'
codeBlockLayoutdefaults to wrapping, so a port there would show the toggle only withscrollable.The feature was first shipped in T3 Code's mobile app through pingdotgg#1, by @Abdullah1738.
Review changes (
dda187bf,2d2927e0)On by default.
enableCodeBlockWrapTogglenow defaults totrue(codegen spec, JS default, Android@ReactPropand measure-pass fallback, and the native wrap-state helpers); apps that don't want the toggle passfalse. Per the inline comment on feat: add a per-block line wrap toggle to code block headers #1010, plus the accessibility doc wording from the other one.One owner for the wrap state. A per-view
CodeBlockWrapState(Android) /ENRMCodeBlockWrapState(iOS) holds the wrapped set and is its only writer. The code block view only reports the tap (or the VoiceOver action); it no longer flipswrappeditself, so a missed index lookup can't leave a wrapped view measured as unwrapped.Nested code blocks in blockquotes and admonitions get the toggle on both platforms, with matching measurement and accessibility labels.
One reset rule on both platforms: compare the new
markdownprop with the previous prop, and keep the state when the prop equals what's rendered. Before, Android compared with the rendered document, so an app that ignored a checkbox tap and kept streaming its own copy unwrapped the blocks.Allocation-free prefix check. The reset check compared via
substringBeforeLast/substr, which copied almost the whole previous document on every streamed chunk while a block was wrapped. It now compares in place.Android re-measure after a reset. Yoga measures the new markdown before the prop reaches the view, so it measured with the old wrapped set (replacing the document left space for a wrapped first block). A prop reset now asks Yoga to measure again, as iOS already did.
iOS language label is capped before the header buttons and truncated with an ellipsis, like Android, instead of running under the wrap button.
Example app (Android):
MainActivitynow handlesfontScaleitself; before, a system font change recreated the activity and crashed in react-native-screens' fragment restore.Unit tests for the reset rule and the nested counting order (Android
CodeBlockWrapStateTest, iOSENRMCodeBlockWrapStateTests).Also in this PR
d25182f0fixes a pre-existing iOS bug this feature surfaced:toggleTaskListItemAtIndex(ios/utils/TaskListTapUtils.m) wrote the inverse checkbox state into the cached markdown since #92. The helper is shared, soEnrichedMarkdownTextchanges too. Release notes line:Testing
:react-native-enriched-markdown:testDebugUnitTestandktlintCheckpass.yarn test:ios) pass; clang-format is clean on the touched files.fontScalechange came out of it.Screenshots (from #1010)
Conversation on #1010
Copied here so it's readable without opening #1010. Comments are quoted verbatim, oldest first.
@hryhoriiK97 · comment · Oct 16 (link)
@hryhoriiK97 · approved · Oct 16
@eszlamczyk · inline on
docs-md/API_REFERENCE.md· Oct 16 (link)@eszlamczyk · inline on
docs/docs/user-experience/accessibility.md· Oct 16 (link)@eszlamczyk · changes requested · Oct 16 (link)
PR Checklist