Reduce <Text> nesting (#6615)
* Move isOnlyEmoji out of RichText To fix Fast Refresh. * Make renderChildrenWithEmoji work with any children * Always go through UITextView for consistency It already contains the `selectable` and `iOS` checks inside. * Move `emoji` check into `renderChildrenWithEmoji` * Remove unnecessary intermediate UITextView nodes * Make childHasEmoji check recursive It didn't handle nested arrays etc correctly before. * Remove the "children must be string" limitation Should not be necessary now that we correctly handle nested arrays etc. * Fix unnecessary regex reallocation This doesn't have a global flag so it's okay to reuse. * Remove unnecessary <Text> wrapper in RichText
This commit is contained in:
+42
-51
@@ -1,10 +1,11 @@
|
||||
import React from 'react'
|
||||
import React, {Children} from 'react'
|
||||
import {TextProps as RNTextProps} from 'react-native'
|
||||
import {StyleProp, TextStyle} from 'react-native'
|
||||
import {UITextView} from 'react-native-uitextview'
|
||||
import createEmojiRegex from 'emoji-regex'
|
||||
|
||||
import {isNative} from '#/platform/detection'
|
||||
import {isIOS} from '#/platform/detection'
|
||||
import {Alf, applyFonts, atoms, flatten} from '#/alf'
|
||||
|
||||
/**
|
||||
@@ -57,7 +58,7 @@ export function normalizeTextStyles(
|
||||
}
|
||||
|
||||
export type StringChild = string | (string | null)[]
|
||||
export type TextProps = Omit<RNTextProps, 'children'> & {
|
||||
export type TextProps = RNTextProps & {
|
||||
/**
|
||||
* Lets the user select text, to use the native copy and paste functionality.
|
||||
*/
|
||||
@@ -71,65 +72,55 @@ export type TextProps = Omit<RNTextProps, 'children'> & {
|
||||
* Appears as a small tooltip on web hover.
|
||||
*/
|
||||
title?: string
|
||||
} & (
|
||||
| {
|
||||
emoji?: true
|
||||
children: StringChild
|
||||
}
|
||||
| {
|
||||
emoji?: false
|
||||
children: RNTextProps['children']
|
||||
}
|
||||
)
|
||||
/**
|
||||
* Whether the children could possibly contain emoji.
|
||||
*/
|
||||
emoji?: boolean
|
||||
}
|
||||
|
||||
const EMOJI = createEmojiRegex()
|
||||
|
||||
export function childHasEmoji(children: React.ReactNode) {
|
||||
return (Array.isArray(children) ? children : [children]).some(
|
||||
child => typeof child === 'string' && createEmojiRegex().test(child),
|
||||
)
|
||||
}
|
||||
|
||||
export function childIsString(
|
||||
children: React.ReactNode,
|
||||
): children is StringChild {
|
||||
return (
|
||||
typeof children === 'string' ||
|
||||
(Array.isArray(children) &&
|
||||
children.every(child => typeof child === 'string' || child === null))
|
||||
)
|
||||
let hasEmoji = false
|
||||
Children.forEach(children, child => {
|
||||
if (typeof child === 'string' && createEmojiRegex().test(child)) {
|
||||
hasEmoji = true
|
||||
}
|
||||
})
|
||||
return hasEmoji
|
||||
}
|
||||
|
||||
export function renderChildrenWithEmoji(
|
||||
children: StringChild,
|
||||
children: React.ReactNode,
|
||||
props: Omit<TextProps, 'children'> = {},
|
||||
emoji: boolean,
|
||||
) {
|
||||
const normalized = Array.isArray(children) ? children : [children]
|
||||
if (!isIOS || !emoji) {
|
||||
return children
|
||||
}
|
||||
return Children.map(children, child => {
|
||||
if (typeof child !== 'string') return child
|
||||
|
||||
return (
|
||||
<UITextView {...props}>
|
||||
{normalized.map(child => {
|
||||
if (typeof child !== 'string') return child
|
||||
const emojis = child.match(EMOJI)
|
||||
|
||||
const emojis = child.match(EMOJI)
|
||||
if (emojis === null) {
|
||||
return child
|
||||
}
|
||||
|
||||
if (emojis === null) {
|
||||
return child
|
||||
}
|
||||
|
||||
return child.split(EMOJI).map((stringPart, index) => (
|
||||
<UITextView key={index} {...props}>
|
||||
{stringPart}
|
||||
{emojis[index] ? (
|
||||
<UITextView
|
||||
{...props}
|
||||
style={[props?.style, {color: 'black', fontFamily: 'System'}]}>
|
||||
{emojis[index]}
|
||||
</UITextView>
|
||||
) : null}
|
||||
</UITextView>
|
||||
))
|
||||
})}
|
||||
</UITextView>
|
||||
)
|
||||
return child.split(EMOJI).map((stringPart, index) => [
|
||||
stringPart,
|
||||
emojis[index] ? (
|
||||
<UITextView
|
||||
{...props}
|
||||
style={[props?.style, {color: 'black', fontFamily: 'System'}]}>
|
||||
{emojis[index]}
|
||||
</UITextView>
|
||||
) : null,
|
||||
])
|
||||
})
|
||||
}
|
||||
|
||||
const SINGLE_EMOJI_RE = /^[\p{Emoji_Presentation}\p{Extended_Pictographic}]+$/u
|
||||
export function isOnlyEmoji(text: string) {
|
||||
return text.length <= 15 && SINGLE_EMOJI_RE.test(text)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user