diff --git a/apps/mobile/modules/t3-markdown-text/src/NativeMarkdownBlock.tsx b/apps/mobile/modules/t3-markdown-text/src/NativeMarkdownBlock.tsx index b57a182dbc56c..dd3926e4ed339 100644 --- a/apps/mobile/modules/t3-markdown-text/src/NativeMarkdownBlock.tsx +++ b/apps/mobile/modules/t3-markdown-text/src/NativeMarkdownBlock.tsx @@ -5,6 +5,7 @@ import type { MarkdownNode } from "react-native-nitro-markdown/headless"; import { CopyTextButton } from "./CopyTextButton"; import { MarkdownTextPrimitive } from "./MarkdownTextPrimitive"; import { + nativeMarkdownDocumentChunks, nativeMarkdownDocumentRuns, nativeMarkdownListItemBlocks, nativeMarkdownNodePosition, @@ -57,10 +58,11 @@ function SelectableNode(props: { readonly skills: ReadonlyArray; readonly textStyle: NativeMarkdownTextStyle; readonly onLinkPress?: (href: string) => void; + readonly depth?: number; }) { return ( @@ -567,18 +569,34 @@ export function NativeMarkdownBlock(props: { gap: 6, }} > - {(props.node.children ?? []).map((child, index) => ( - - ))} + {/* Consecutive plain children share one text view so selection can + cross paragraphs; iOS selection cannot span separate text views. */} + {nativeMarkdownDocumentChunks({ + type: "document", + children: props.node.children ?? [], + }).map((chunk) => + chunk.kind === "selectable" ? ( + + ) : ( + + ), + )} ); case "list": diff --git a/apps/mobile/modules/t3-markdown-text/src/nativeMarkdownText.ts b/apps/mobile/modules/t3-markdown-text/src/nativeMarkdownText.ts index 04682874db0c7..c88aea9b764c7 100644 --- a/apps/mobile/modules/t3-markdown-text/src/nativeMarkdownText.ts +++ b/apps/mobile/modules/t3-markdown-text/src/nativeMarkdownText.ts @@ -945,8 +945,9 @@ export function nativeMarkdownChunkSpacing( export function nativeMarkdownDocumentRuns( node: MarkdownNode, skills: ReadonlyArray = [], + depth = 0, ): ReadonlyArray { - const runs = appendDocumentBlock([], node); + const runs = appendDocumentBlock([], node, depth); while (runs.length > 0) { const lastIndex = runs.length - 1; const last = runs[lastIndex]; diff --git a/apps/mobile/src/lib/nativeMarkdownText.test.ts b/apps/mobile/src/lib/nativeMarkdownText.test.ts index d934a306aa01d..19c57f4e6c223 100644 --- a/apps/mobile/src/lib/nativeMarkdownText.test.ts +++ b/apps/mobile/src/lib/nativeMarkdownText.test.ts @@ -669,6 +669,25 @@ describe("nativeMarkdownDocumentChunks", () => { ]); }); + it("groups plain blockquote children into one selectable chunk so selection can span paragraphs", () => { + const paragraph = (content: string): MarkdownNode => ({ + type: "paragraph", + children: [{ type: "text", content }], + }); + const code: MarkdownNode = { type: "code_block", content: "const a = 1;" }; + + const chunks = nativeMarkdownDocumentChunks({ + type: "document", + children: [paragraph("First"), paragraph("Second"), code, paragraph("Third")], + }); + + expect(chunks.map((chunk) => [chunk.kind, chunk.node.children?.length ?? 0])).toEqual([ + ["selectable", 2], + ["rich", 0], + ["selectable", 1], + ]); + }); + it("keeps headings and plain lists in one selectable document", () => { const document: MarkdownNode = { type: "document",