Fix iOS markdown style metadata warnings
Resolve markdown UITextView styles to plain React Native text styles before crossing the third-party boundary. react-native-uitextview flattens parent and child styles internally, so Unistyles metadata must not be carried into that handoff.
This commit is contained in:
@@ -0,0 +1,32 @@
|
|||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
import { StyleSheet, type TextStyle } from "react-native";
|
||||||
|
import { resolvePlainMarkdownTextStyle } from "@/components/markdown-text-style";
|
||||||
|
|
||||||
|
function unistylesStyle(id: string, style: Record<string, unknown>) {
|
||||||
|
return {
|
||||||
|
...style,
|
||||||
|
[`unistyles_${id}`]: { id },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function uiTextViewFlatten(rootStyle: TextStyle, style: TextStyle): Record<string, unknown> {
|
||||||
|
// react-native-uitextview/src/util.ts:8 flattens [rootStyle, style]
|
||||||
|
// before passing the result to its native View-backed components.
|
||||||
|
return { ...(StyleSheet.flatten([rootStyle, style]) as Record<string, unknown>) };
|
||||||
|
}
|
||||||
|
|
||||||
|
function unistylesMetadataKeys(style: Record<string, unknown>) {
|
||||||
|
return Object.keys(style).filter((key) => key.startsWith("unistyles_"));
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("resolvePlainMarkdownTextStyle", () => {
|
||||||
|
it("keeps UITextView from collapsing parent and child Unistyles styles into one native View style object", () => {
|
||||||
|
const merged = uiTextViewFlatten(
|
||||||
|
resolvePlainMarkdownTextStyle(unistylesStyle("paragraph", { color: "#111" })),
|
||||||
|
resolvePlainMarkdownTextStyle(unistylesStyle("text", { fontWeight: "600" })),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(unistylesMetadataKeys(merged)).toHaveLength(0);
|
||||||
|
expect(merged).toMatchObject({ color: "#111", fontWeight: "600" });
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { StyleSheet, type StyleProp, type TextStyle } from "react-native";
|
||||||
|
|
||||||
|
export function resolvePlainMarkdownTextStyle(style: StyleProp<TextStyle>): TextStyle {
|
||||||
|
return stripUnistylesMetadata(StyleSheet.flatten(style) ?? {});
|
||||||
|
}
|
||||||
|
|
||||||
|
function stripUnistylesMetadata(style: TextStyle): TextStyle {
|
||||||
|
// iOS markdown text goes through react-native-uitextview. That library
|
||||||
|
// inherits text styles by flattening [parentStyle, childStyle] before handing
|
||||||
|
// the result to native View-backed components. If both entries are Unistyles
|
||||||
|
// styles, flattening preserves both `unistyles_*` metadata keys in one object,
|
||||||
|
// and Unistyles correctly warns that the style should have stayed array-shaped.
|
||||||
|
//
|
||||||
|
// `UITextView` is a third-party boundary, not a Unistyles-tracked component in
|
||||||
|
// our ownership model. Resolve the concrete style values before crossing that
|
||||||
|
// boundary and drop only Unistyles' private tracking metadata. This preserves
|
||||||
|
// iOS paragraph/spanning text selection while avoiding the metadata merge.
|
||||||
|
const plainStyle: Record<string, unknown> = { ...style };
|
||||||
|
for (const key of Object.keys(plainStyle)) {
|
||||||
|
if (key.startsWith("unistyles_")) {
|
||||||
|
delete plainStyle[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return plainStyle as TextStyle;
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import { useMemo, type ReactNode } from "react";
|
import { useMemo, type ReactNode } from "react";
|
||||||
import { View, type StyleProp, type TextProps, type TextStyle, type ViewStyle } from "react-native";
|
import { View, type StyleProp, type TextProps, type TextStyle, type ViewStyle } from "react-native";
|
||||||
import { UITextView } from "react-native-uitextview";
|
import { UITextView } from "react-native-uitextview";
|
||||||
|
import { resolvePlainMarkdownTextStyle } from "@/components/markdown-text-style";
|
||||||
|
|
||||||
interface MarkdownTextSpanProps {
|
interface MarkdownTextSpanProps {
|
||||||
style?: StyleProp<TextStyle>;
|
style?: StyleProp<TextStyle>;
|
||||||
@@ -26,11 +27,13 @@ export function MarkdownTextSpan({
|
|||||||
onPress,
|
onPress,
|
||||||
accessibilityRole,
|
accessibilityRole,
|
||||||
}: MarkdownTextSpanProps) {
|
}: MarkdownTextSpanProps) {
|
||||||
|
const plainStyle = useMemo(() => resolvePlainMarkdownTextStyle(style), [style]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<UITextView
|
<UITextView
|
||||||
uiTextView
|
uiTextView
|
||||||
selectable
|
selectable
|
||||||
style={style}
|
style={plainStyle}
|
||||||
onPress={onPress}
|
onPress={onPress}
|
||||||
accessibilityRole={accessibilityRole}
|
accessibilityRole={accessibilityRole}
|
||||||
>
|
>
|
||||||
@@ -59,7 +62,11 @@ export function MarkdownParagraphView({
|
|||||||
children,
|
children,
|
||||||
}: MarkdownParagraphViewProps) {
|
}: MarkdownParagraphViewProps) {
|
||||||
const textStyle = useMemo(
|
const textStyle = useMemo(
|
||||||
() => [paragraphStyle, MARKDOWN_PARAGRAPH_RESET] as StyleProp<TextStyle>,
|
() =>
|
||||||
|
resolvePlainMarkdownTextStyle([
|
||||||
|
paragraphStyle,
|
||||||
|
MARKDOWN_PARAGRAPH_RESET,
|
||||||
|
] as StyleProp<TextStyle>),
|
||||||
[paragraphStyle],
|
[paragraphStyle],
|
||||||
);
|
);
|
||||||
const viewStyle = useMemo(() => [paragraphStyle, MARKDOWN_PARAGRAPH_RESET], [paragraphStyle]);
|
const viewStyle = useMemo(() => [paragraphStyle, MARKDOWN_PARAGRAPH_RESET], [paragraphStyle]);
|
||||||
|
|||||||
Reference in New Issue
Block a user