Created
October 4, 2024 20:05
-
-
Save haileyok/dd1c79024dc72cc15101c11b89128e32 to your computer and use it in GitHub Desktop.
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
import React from 'react' | |
import { | |
Platform, | |
requireNativeComponent, | |
StyleSheet, | |
UIManager, | |
ViewStyle, | |
TextProps, | |
Text as RNText | |
} from 'react-native' | |
const LINKING_ERROR = | |
`The package 'react-native-uitextview' doesn't seem to be linked. Make sure: \n\n` + | |
Platform.select({ios: "- You have run 'pod install'\n", default: ''}) + | |
'- You rebuilt the app after installing the package\n' + | |
'- You are not using Expo Go\n' | |
// These props are for the main native wrapper component | |
export interface RNUITextViewProps extends TextProps { | |
children: React.ReactNode | |
style: ViewStyle[] | |
} | |
// These props are for each of the children native components | |
type RNUITextViewChildProps = TextProps & { | |
text: string | |
onTextPress?: (...args: any[]) => void | |
onTextLongPress?: (...args: any[]) => void | |
} | |
const RNUITextView = | |
UIManager.getViewManagerConfig?.('RNUITextView') != null | |
? requireNativeComponent<RNUITextViewProps>('RNUITextView') | |
: () => { | |
if (Platform.OS !== 'ios') return null | |
throw new Error(LINKING_ERROR) | |
} | |
export const RNUITextViewChild = | |
UIManager.getViewManagerConfig?.('RNUITextViewChild') != null | |
? requireNativeComponent<RNUITextViewChildProps>('RNUITextViewChild') | |
: () => { | |
if (Platform.OS !== 'ios') return null | |
throw new Error(LINKING_ERROR) | |
} | |
const TextAncestorContext = React.createContext<[boolean, ViewStyle]>([ | |
false, | |
StyleSheet.create({}) | |
]) | |
const useTextAncestorContext = () => React.useContext(TextAncestorContext) | |
const textDefaults: TextProps = { | |
allowFontScaling: true, | |
selectable: true | |
} | |
function UITextViewChild({ | |
style, | |
children, | |
...rest | |
}: TextProps & { | |
uiTextView?: boolean | |
}) { | |
const [isAncestor, rootStyle] = useTextAncestorContext() | |
// Flatten the styles, and apply the root styles when needed | |
const flattenedStyle = React.useMemo( | |
() => StyleSheet.flatten([rootStyle, style]), | |
[rootStyle, style] | |
) | |
if (!isAncestor) { | |
return ( | |
<TextAncestorContext.Provider value={[true, flattenedStyle]}> | |
<RNUITextView | |
{...textDefaults} | |
{...rest} | |
ellipsizeMode={rest.ellipsizeMode ?? rest.lineBreakMode ?? 'tail'} | |
style={[flattenedStyle]} | |
onPress={undefined} // We want these to go to the children only | |
onLongPress={undefined}> | |
{parseChildren(children, flattenedStyle, rest)} | |
</RNUITextView> | |
</TextAncestorContext.Provider> | |
) | |
} else { | |
return parseChildren(children, flattenedStyle, rest) | |
} | |
} | |
function UITextViewInner( | |
props: TextProps & { | |
uiTextView?: boolean | |
} | |
) { | |
const [isAncestor] = useTextAncestorContext() | |
// Even if the uiTextView prop is set, we can still default to using | |
// normal selection (i.e. base RN text) if the text doesn't need to be | |
// selectable | |
if ((!props.selectable || !props.uiTextView) && !isAncestor) { | |
return <RNText {...props} /> | |
} | |
return <UITextViewChild {...props} /> | |
} | |
export function UITextView(props: TextProps & {uiTextView?: boolean}) { | |
if (Platform.OS !== 'ios') { | |
return <RNText {...props} /> | |
} | |
return <UITextViewInner {...props} /> | |
} | |
function isReactFragment(variableToInspect) { | |
if (variableToInspect.type) { | |
return variableToInspect.type === React.Fragment; | |
} | |
return variableToInspect === React.Fragment; | |
} | |
function parseChildren(children: React.ReactNode, flattenedStyle: ViewStyle, rest: TextProps) { | |
return React.Children.toArray(children).map((c, index) => { | |
if (isReactFragment(c)) { | |
const children = React.Children.toArray(c.props.children) | |
if (typeof children[0] === 'string') { | |
return ( | |
<RNUITextViewChild | |
key={index} | |
style={flattenedStyle} | |
text={children[0].toString()} | |
{...rest} | |
/> | |
) | |
} | |
} else if (React.isValidElement(c)) { | |
return c | |
} else if (typeof c === 'string' || typeof c === 'number') { | |
return ( | |
<RNUITextViewChild | |
key={index} | |
style={flattenedStyle} | |
text={c.toString()} | |
{...rest} | |
/> | |
) | |
} | |
return null | |
}) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment