properly handle link press events
This commit is contained in:
@@ -10,7 +10,9 @@ public class ExpoSelectableTextModule: Module {
|
|||||||
Prop("segments") { (view: ExpoSelectableTextView, prop: String) in
|
Prop("segments") { (view: ExpoSelectableTextView, prop: String) in
|
||||||
// Convert the JSON to segments
|
// Convert the JSON to segments
|
||||||
if let data = prop.data(using: .utf8) {
|
if let data = prop.data(using: .utf8) {
|
||||||
|
print("first")
|
||||||
if let segments = try? JSONDecoder().decode(TextSegments.self, from: data) {
|
if let segments = try? JSONDecoder().decode(TextSegments.self, from: data) {
|
||||||
|
print("second")
|
||||||
view.segments = segments.segments
|
view.segments = segments.segments
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -27,6 +29,10 @@ public class ExpoSelectableTextModule: Module {
|
|||||||
Prop("selectable") { (view: ExpoSelectableTextView, prop: Bool) in
|
Prop("selectable") { (view: ExpoSelectableTextView, prop: Bool) in
|
||||||
view.textView.isSelectable = prop
|
view.textView.isSelectable = prop
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Prop("children") { (view: ExpoSelectableTextView, prop: JavaScriptValue) in
|
||||||
|
print(prop)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,11 @@ import {
|
|||||||
ExpoProTextSegment,
|
ExpoProTextSegment,
|
||||||
ExpoProTextViewProps,
|
ExpoProTextViewProps,
|
||||||
} from './ExpoSelectableText.types'
|
} from './ExpoSelectableText.types'
|
||||||
import {Text, View} from 'react-native'
|
import {StyleSheet, View} from 'react-native'
|
||||||
|
import {onTextLinkPress, TextLink} from 'view/com/util/Link'
|
||||||
|
import {useNavigation} from '@react-navigation/native'
|
||||||
|
import {NavigationProp} from 'lib/routes/types'
|
||||||
|
import {useModalControls} from 'state/modals'
|
||||||
|
|
||||||
const NativeView: React.ComponentType<ExpoProTextNativeViewProps> =
|
const NativeView: React.ComponentType<ExpoProTextNativeViewProps> =
|
||||||
requireNativeViewManager('ExpoSelectableText')
|
requireNativeViewManager('ExpoSelectableText')
|
||||||
@@ -16,11 +20,18 @@ const NativeView: React.ComponentType<ExpoProTextNativeViewProps> =
|
|||||||
export default function ExpoSelectableTextView({
|
export default function ExpoSelectableTextView({
|
||||||
style,
|
style,
|
||||||
children,
|
children,
|
||||||
selectable = false,
|
selectable = true,
|
||||||
onPress,
|
onPress,
|
||||||
onLongPress,
|
onLongPress,
|
||||||
}: ExpoProTextViewProps) {
|
}: ExpoProTextViewProps) {
|
||||||
|
// Dimensions based on the native view's text height
|
||||||
const [dims, setDims] = React.useState({height: 0})
|
const [dims, setDims] = React.useState({height: 0})
|
||||||
|
|
||||||
|
// Needed for navigation on link presses
|
||||||
|
const navigation = useNavigation<NavigationProp>()
|
||||||
|
const {openModal, closeModal} = useModalControls()
|
||||||
|
|
||||||
|
// Store the callbacks for onPress and onLongPress events
|
||||||
const segmentPressCallbacks = React.useRef<
|
const segmentPressCallbacks = React.useRef<
|
||||||
Array<{index: number; onPress: () => void}>
|
Array<{index: number; onPress: () => void}>
|
||||||
>([])
|
>([])
|
||||||
@@ -28,73 +39,108 @@ export default function ExpoSelectableTextView({
|
|||||||
Array<{index: number; onLongPress: () => void}>
|
Array<{index: number; onLongPress: () => void}>
|
||||||
>([])
|
>([])
|
||||||
|
|
||||||
const onTextLayout = React.useCallback((e: ExpoProTextLayoutEvent) => {
|
// The root style, stringified
|
||||||
console.log('layout')
|
const rootStyle = React.useMemo(() => {
|
||||||
setDims({
|
return style ? JSON.stringify(style) : undefined
|
||||||
height: e.nativeEvent.height,
|
}, [style])
|
||||||
})
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const onTextPress = React.useCallback((e: ExpoProTextPressEvent) => {
|
|
||||||
const onPressSegment = segmentPressCallbacks.current.find(
|
|
||||||
s => s.index === e.nativeEvent.index,
|
|
||||||
)
|
|
||||||
onPressSegment?.onPress()
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
|
// The text segments, stringified
|
||||||
const textSegments = React.useMemo(() => {
|
const textSegments = React.useMemo(() => {
|
||||||
const segments: ExpoProTextSegment[] = []
|
const segments: ExpoProTextSegment[] = []
|
||||||
|
|
||||||
for (const [index, child] of React.Children.toArray(children).entries()) {
|
for (const [index, child] of React.Children.toArray(children).entries()) {
|
||||||
|
// Most of our children will be strings. Simply add them to the segments array.
|
||||||
if (typeof child === 'string') {
|
if (typeof child === 'string') {
|
||||||
segments.push({
|
segments.push({
|
||||||
index,
|
index,
|
||||||
text: child,
|
text: child,
|
||||||
style,
|
style: style,
|
||||||
handlePress: onPress !== undefined,
|
handlePress: onPress !== undefined,
|
||||||
handleLongPress: onLongPress !== undefined,
|
handleLongPress: onLongPress !== undefined,
|
||||||
})
|
})
|
||||||
} else if (
|
} else if (React.isValidElement(child)) {
|
||||||
React.isValidElement(child) &&
|
// If it is a child, it is either a nested <Text> or a <TextLink>. Check if the child is a string or a <TextLink>
|
||||||
(child as React.ReactElement<any>).type === Text
|
// If it's a <TextLink> we need to create on the onPress handler (it won't be created in the component since the
|
||||||
) {
|
// component never actually gets rendered)
|
||||||
const {onPress, onLongPress, children, style} = child.props
|
|
||||||
|
|
||||||
segments.push({
|
const {
|
||||||
index,
|
children: innerChildren,
|
||||||
text: children,
|
onLongPress: innerOnLongPress,
|
||||||
style,
|
style: innerStyle,
|
||||||
handlePress: onPress !== undefined,
|
text: innerText,
|
||||||
handleLongPress: onLongPress !== undefined,
|
href,
|
||||||
})
|
navigationAction,
|
||||||
|
warnOnMismatchingLabel,
|
||||||
|
} = child.props
|
||||||
|
let innerOnPress = child.props.onPress
|
||||||
|
|
||||||
if (onPress !== undefined) {
|
const type = (child as React.ReactElement<any>).type
|
||||||
segmentPressCallbacks.current.push({
|
|
||||||
|
if (typeof innerChildren === 'string' || type === TextLink) {
|
||||||
|
if (type === TextLink) {
|
||||||
|
// Set the onPress handler
|
||||||
|
innerOnPress = () => {
|
||||||
|
onTextLinkPress({
|
||||||
|
openModal,
|
||||||
|
closeModal,
|
||||||
|
text: innerText,
|
||||||
|
navigation,
|
||||||
|
href,
|
||||||
|
navigationAction,
|
||||||
|
warnOnMismatchingLabel,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the segment to the array
|
||||||
|
segments.push({
|
||||||
index,
|
index,
|
||||||
onPress,
|
text: innerText ?? innerChildren,
|
||||||
|
style: StyleSheet.flatten(innerStyle),
|
||||||
|
handlePress: innerOnPress !== undefined,
|
||||||
|
handleLongPress: innerOnLongPress !== undefined,
|
||||||
})
|
})
|
||||||
}
|
|
||||||
|
|
||||||
if (onLongPress !== undefined) {
|
// If we have press events, push them in
|
||||||
segmentLongPressCallbacks.current.push({
|
if (innerOnPress !== undefined) {
|
||||||
index,
|
segmentPressCallbacks.current.push({
|
||||||
onLongPress,
|
index,
|
||||||
})
|
onPress: innerOnPress,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (onLongPress !== undefined) {
|
||||||
|
segmentLongPressCallbacks.current.push({
|
||||||
|
index,
|
||||||
|
onLongPress: innerOnLongPress,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return segments
|
return segments
|
||||||
}, [children, onLongPress, onPress, style])
|
}, [children, closeModal, navigation, onLongPress, onPress, openModal, style])
|
||||||
|
|
||||||
const segmentsJson = React.useMemo(() => {
|
const segmentsJson = React.useMemo(() => {
|
||||||
const json = JSON.stringify({segments: textSegments})
|
return JSON.stringify({segments: textSegments})
|
||||||
return json
|
|
||||||
}, [textSegments])
|
}, [textSegments])
|
||||||
|
|
||||||
const rootStyle = React.useMemo(() => {
|
const onTextLayout = React.useCallback((e: ExpoProTextLayoutEvent) => {
|
||||||
return style ? JSON.stringify(style) : undefined
|
setDims({
|
||||||
}, [style])
|
height: e.nativeEvent.height,
|
||||||
|
})
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const onTextPress = React.useCallback(
|
||||||
|
(e: ExpoProTextPressEvent) => {
|
||||||
|
const onPressSegment =
|
||||||
|
segmentPressCallbacks.current.find(s => s.index === e.nativeEvent.index)
|
||||||
|
?.onPress ?? onPress
|
||||||
|
|
||||||
|
onPressSegment?.()
|
||||||
|
},
|
||||||
|
[onPress],
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={[dims, {width: '100%'}]}>
|
<View style={[dims, {width: '100%'}]}>
|
||||||
|
|||||||
@@ -108,7 +108,8 @@ export function RichText({
|
|||||||
style={[style, pal.text, lineHeightStyle]}
|
style={[style, pal.text, lineHeightStyle]}
|
||||||
numberOfLines={numberOfLines}
|
numberOfLines={numberOfLines}
|
||||||
// @ts-ignore web only -prf
|
// @ts-ignore web only -prf
|
||||||
dataSet={WORD_WRAP}>
|
dataSet={WORD_WRAP}
|
||||||
|
selectable={selectable}>
|
||||||
{els}
|
{els}
|
||||||
</Text>
|
</Text>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -27,8 +27,6 @@ export function Text({
|
|||||||
const typography = theme.typography[type]
|
const typography = theme.typography[type]
|
||||||
const lineHeightStyle = lineHeight ? lh(theme, type, lineHeight) : undefined
|
const lineHeightStyle = lineHeight ? lh(theme, type, lineHeight) : undefined
|
||||||
|
|
||||||
// {"color":"#000000","fontSize":20,"letterSpacing":0.2,"fontWeight":"400","flex":1,"lineHeight":26}
|
|
||||||
|
|
||||||
// if (false) {
|
// if (false) {
|
||||||
// TODO remove
|
// TODO remove
|
||||||
if (selectable && isIOS) {
|
if (selectable && isIOS) {
|
||||||
|
|||||||
Reference in New Issue
Block a user