mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Make use of ref-as-prop support in Text (#51361)
Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/51361 Make use of the React 19 feature so that we can remove the remaining `forwardRef` in react native. Changelog: [Internal] Reviewed By: yungsters Differential Revision: D74810512 fbshipit-source-id: 0f902da8b487db353609b8de14f46a4cec23a46b
This commit is contained in:
committed by
Facebook GitHub Bot
parent
797705c00d
commit
2fa2c2071c
+260
-249
@@ -38,228 +38,153 @@ type TextForwardRef = React.ElementRef<
|
||||
const TextImpl: component(
|
||||
ref?: React.RefSetter<TextForwardRef>,
|
||||
...props: TextProps
|
||||
) = React.forwardRef(
|
||||
(
|
||||
{
|
||||
accessible,
|
||||
accessibilityLabel,
|
||||
accessibilityState,
|
||||
allowFontScaling,
|
||||
'aria-busy': ariaBusy,
|
||||
'aria-checked': ariaChecked,
|
||||
'aria-disabled': ariaDisabled,
|
||||
'aria-expanded': ariaExpanded,
|
||||
'aria-label': ariaLabel,
|
||||
'aria-selected': ariaSelected,
|
||||
children,
|
||||
ellipsizeMode,
|
||||
disabled,
|
||||
id,
|
||||
nativeID,
|
||||
numberOfLines,
|
||||
onLongPress,
|
||||
onPress,
|
||||
onPressIn,
|
||||
onPressOut,
|
||||
onResponderGrant,
|
||||
onResponderMove,
|
||||
onResponderRelease,
|
||||
onResponderTerminate,
|
||||
onResponderTerminationRequest,
|
||||
onStartShouldSetResponder,
|
||||
pressRetentionOffset,
|
||||
selectable,
|
||||
selectionColor,
|
||||
suppressHighlighting,
|
||||
style,
|
||||
...restProps
|
||||
}: TextProps,
|
||||
forwardedRef,
|
||||
) => {
|
||||
const _accessibilityLabel = ariaLabel ?? accessibilityLabel;
|
||||
) = ({
|
||||
ref: forwardedRef,
|
||||
accessible,
|
||||
accessibilityLabel,
|
||||
accessibilityState,
|
||||
allowFontScaling,
|
||||
'aria-busy': ariaBusy,
|
||||
'aria-checked': ariaChecked,
|
||||
'aria-disabled': ariaDisabled,
|
||||
'aria-expanded': ariaExpanded,
|
||||
'aria-label': ariaLabel,
|
||||
'aria-selected': ariaSelected,
|
||||
children,
|
||||
ellipsizeMode,
|
||||
disabled,
|
||||
id,
|
||||
nativeID,
|
||||
numberOfLines,
|
||||
onLongPress,
|
||||
onPress,
|
||||
onPressIn,
|
||||
onPressOut,
|
||||
onResponderGrant,
|
||||
onResponderMove,
|
||||
onResponderRelease,
|
||||
onResponderTerminate,
|
||||
onResponderTerminationRequest,
|
||||
onStartShouldSetResponder,
|
||||
pressRetentionOffset,
|
||||
selectable,
|
||||
selectionColor,
|
||||
suppressHighlighting,
|
||||
style,
|
||||
...restProps
|
||||
}: {
|
||||
ref?: React.RefSetter<TextForwardRef>,
|
||||
...TextProps,
|
||||
}) => {
|
||||
const _accessibilityLabel = ariaLabel ?? accessibilityLabel;
|
||||
|
||||
let _accessibilityState: ?TextProps['accessibilityState'] =
|
||||
accessibilityState;
|
||||
if (
|
||||
ariaBusy != null ||
|
||||
ariaChecked != null ||
|
||||
ariaDisabled != null ||
|
||||
ariaExpanded != null ||
|
||||
ariaSelected != null
|
||||
) {
|
||||
if (_accessibilityState != null) {
|
||||
_accessibilityState = {
|
||||
busy: ariaBusy ?? _accessibilityState.busy,
|
||||
checked: ariaChecked ?? _accessibilityState.checked,
|
||||
disabled: ariaDisabled ?? _accessibilityState.disabled,
|
||||
expanded: ariaExpanded ?? _accessibilityState.expanded,
|
||||
selected: ariaSelected ?? _accessibilityState.selected,
|
||||
};
|
||||
} else {
|
||||
_accessibilityState = {
|
||||
busy: ariaBusy,
|
||||
checked: ariaChecked,
|
||||
disabled: ariaDisabled,
|
||||
expanded: ariaExpanded,
|
||||
selected: ariaSelected,
|
||||
};
|
||||
}
|
||||
let _accessibilityState: ?TextProps['accessibilityState'] =
|
||||
accessibilityState;
|
||||
if (
|
||||
ariaBusy != null ||
|
||||
ariaChecked != null ||
|
||||
ariaDisabled != null ||
|
||||
ariaExpanded != null ||
|
||||
ariaSelected != null
|
||||
) {
|
||||
if (_accessibilityState != null) {
|
||||
_accessibilityState = {
|
||||
busy: ariaBusy ?? _accessibilityState.busy,
|
||||
checked: ariaChecked ?? _accessibilityState.checked,
|
||||
disabled: ariaDisabled ?? _accessibilityState.disabled,
|
||||
expanded: ariaExpanded ?? _accessibilityState.expanded,
|
||||
selected: ariaSelected ?? _accessibilityState.selected,
|
||||
};
|
||||
} else {
|
||||
_accessibilityState = {
|
||||
busy: ariaBusy,
|
||||
checked: ariaChecked,
|
||||
disabled: ariaDisabled,
|
||||
expanded: ariaExpanded,
|
||||
selected: ariaSelected,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const _accessibilityStateDisabled = _accessibilityState?.disabled;
|
||||
const _disabled = disabled ?? _accessibilityStateDisabled;
|
||||
const _accessibilityStateDisabled = _accessibilityState?.disabled;
|
||||
const _disabled = disabled ?? _accessibilityStateDisabled;
|
||||
|
||||
const isPressable =
|
||||
(onPress != null ||
|
||||
onLongPress != null ||
|
||||
onStartShouldSetResponder != null) &&
|
||||
_disabled !== true;
|
||||
const isPressable =
|
||||
(onPress != null ||
|
||||
onLongPress != null ||
|
||||
onStartShouldSetResponder != null) &&
|
||||
_disabled !== true;
|
||||
|
||||
// TODO: Move this processing to the view configuration.
|
||||
const _selectionColor =
|
||||
selectionColor != null ? processColor(selectionColor) : undefined;
|
||||
// TODO: Move this processing to the view configuration.
|
||||
const _selectionColor =
|
||||
selectionColor != null ? processColor(selectionColor) : undefined;
|
||||
|
||||
let _style = style;
|
||||
let _style = style;
|
||||
if (__DEV__) {
|
||||
if (PressabilityDebug.isEnabled() && onPress != null) {
|
||||
_style = [style, {color: 'magenta'}];
|
||||
}
|
||||
}
|
||||
|
||||
let _numberOfLines = numberOfLines;
|
||||
if (_numberOfLines != null && !(_numberOfLines >= 0)) {
|
||||
if (__DEV__) {
|
||||
if (PressabilityDebug.isEnabled() && onPress != null) {
|
||||
_style = [style, {color: 'magenta'}];
|
||||
}
|
||||
}
|
||||
|
||||
let _numberOfLines = numberOfLines;
|
||||
if (_numberOfLines != null && !(_numberOfLines >= 0)) {
|
||||
if (__DEV__) {
|
||||
console.error(
|
||||
`'numberOfLines' in <Text> must be a non-negative number, received: ${_numberOfLines}. The value will be set to 0.`,
|
||||
);
|
||||
}
|
||||
_numberOfLines = 0;
|
||||
}
|
||||
|
||||
let _selectable = selectable;
|
||||
|
||||
let processedStyle = flattenStyle<TextStyleProp>(_style);
|
||||
if (processedStyle != null) {
|
||||
let overrides: ?{...TextStyleInternal} = null;
|
||||
if (typeof processedStyle.fontWeight === 'number') {
|
||||
overrides = overrides || ({}: {...TextStyleInternal});
|
||||
overrides.fontWeight =
|
||||
// $FlowFixMe[incompatible-cast]
|
||||
(processedStyle.fontWeight.toString(): TextStyleInternal['fontWeight']);
|
||||
}
|
||||
|
||||
if (processedStyle.userSelect != null) {
|
||||
_selectable = userSelectToSelectableMap[processedStyle.userSelect];
|
||||
overrides = overrides || ({}: {...TextStyleInternal});
|
||||
overrides.userSelect = undefined;
|
||||
}
|
||||
|
||||
if (processedStyle.verticalAlign != null) {
|
||||
overrides = overrides || ({}: {...TextStyleInternal});
|
||||
overrides.textAlignVertical =
|
||||
verticalAlignToTextAlignVerticalMap[processedStyle.verticalAlign];
|
||||
overrides.verticalAlign = undefined;
|
||||
}
|
||||
|
||||
if (overrides != null) {
|
||||
// $FlowFixMe[incompatible-type]
|
||||
_style = [_style, overrides];
|
||||
}
|
||||
}
|
||||
|
||||
const _nativeID = id ?? nativeID;
|
||||
|
||||
const hasTextAncestor = useContext(TextAncestor);
|
||||
if (hasTextAncestor) {
|
||||
if (isPressable) {
|
||||
return (
|
||||
<NativePressableVirtualText
|
||||
ref={forwardedRef}
|
||||
textProps={{
|
||||
...restProps,
|
||||
accessibilityLabel: _accessibilityLabel,
|
||||
accessibilityState: _accessibilityState,
|
||||
nativeID: _nativeID,
|
||||
numberOfLines: _numberOfLines,
|
||||
selectable: _selectable,
|
||||
selectionColor: _selectionColor,
|
||||
style: _style,
|
||||
disabled: disabled,
|
||||
children,
|
||||
}}
|
||||
textPressabilityProps={{
|
||||
onLongPress,
|
||||
onPress,
|
||||
onPressIn,
|
||||
onPressOut,
|
||||
onResponderGrant,
|
||||
onResponderMove,
|
||||
onResponderRelease,
|
||||
onResponderTerminate,
|
||||
onResponderTerminationRequest,
|
||||
onStartShouldSetResponder,
|
||||
pressRetentionOffset,
|
||||
suppressHighlighting,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<NativeVirtualText
|
||||
{...restProps}
|
||||
accessibilityLabel={_accessibilityLabel}
|
||||
accessibilityState={_accessibilityState}
|
||||
nativeID={_nativeID}
|
||||
numberOfLines={_numberOfLines}
|
||||
ref={forwardedRef}
|
||||
selectable={_selectable}
|
||||
selectionColor={_selectionColor}
|
||||
style={_style}
|
||||
disabled={disabled}>
|
||||
{children}
|
||||
</NativeVirtualText>
|
||||
console.error(
|
||||
`'numberOfLines' in <Text> must be a non-negative number, received: ${_numberOfLines}. The value will be set to 0.`,
|
||||
);
|
||||
}
|
||||
_numberOfLines = 0;
|
||||
}
|
||||
|
||||
// If the disabled prop and accessibilityState.disabled are out of sync but not both in
|
||||
// falsy states we need to update the accessibilityState object to use the disabled prop.
|
||||
if (
|
||||
_disabled !== _accessibilityStateDisabled &&
|
||||
((_disabled != null && _disabled !== false) ||
|
||||
(_accessibilityStateDisabled != null &&
|
||||
_accessibilityStateDisabled !== false))
|
||||
) {
|
||||
_accessibilityState = {..._accessibilityState, disabled: _disabled};
|
||||
let _selectable = selectable;
|
||||
|
||||
let processedStyle = flattenStyle<TextStyleProp>(_style);
|
||||
if (processedStyle != null) {
|
||||
let overrides: ?{...TextStyleInternal} = null;
|
||||
if (typeof processedStyle.fontWeight === 'number') {
|
||||
overrides = overrides || ({}: {...TextStyleInternal});
|
||||
overrides.fontWeight =
|
||||
// $FlowFixMe[incompatible-cast]
|
||||
(processedStyle.fontWeight.toString(): TextStyleInternal['fontWeight']);
|
||||
}
|
||||
|
||||
const _accessible = Platform.select({
|
||||
ios: accessible !== false,
|
||||
android:
|
||||
accessible == null
|
||||
? onPress != null || onLongPress != null
|
||||
: accessible,
|
||||
default: accessible,
|
||||
});
|
||||
if (processedStyle.userSelect != null) {
|
||||
_selectable = userSelectToSelectableMap[processedStyle.userSelect];
|
||||
overrides = overrides || ({}: {...TextStyleInternal});
|
||||
overrides.userSelect = undefined;
|
||||
}
|
||||
|
||||
let nativeText = null;
|
||||
if (processedStyle.verticalAlign != null) {
|
||||
overrides = overrides || ({}: {...TextStyleInternal});
|
||||
overrides.textAlignVertical =
|
||||
verticalAlignToTextAlignVerticalMap[processedStyle.verticalAlign];
|
||||
overrides.verticalAlign = undefined;
|
||||
}
|
||||
|
||||
if (overrides != null) {
|
||||
// $FlowFixMe[incompatible-type]
|
||||
_style = [_style, overrides];
|
||||
}
|
||||
}
|
||||
|
||||
const _nativeID = id ?? nativeID;
|
||||
|
||||
const hasTextAncestor = useContext(TextAncestor);
|
||||
if (hasTextAncestor) {
|
||||
if (isPressable) {
|
||||
nativeText = (
|
||||
<NativePressableText
|
||||
return (
|
||||
<NativePressableVirtualText
|
||||
ref={forwardedRef}
|
||||
textProps={{
|
||||
...restProps,
|
||||
accessibilityLabel: _accessibilityLabel,
|
||||
accessibilityState: _accessibilityState,
|
||||
accessible: _accessible,
|
||||
allowFontScaling: allowFontScaling !== false,
|
||||
disabled: _disabled,
|
||||
ellipsizeMode: ellipsizeMode ?? 'tail',
|
||||
nativeID: _nativeID,
|
||||
numberOfLines: _numberOfLines,
|
||||
selectable: _selectable,
|
||||
selectionColor: _selectionColor,
|
||||
style: _style,
|
||||
disabled: disabled,
|
||||
children,
|
||||
}}
|
||||
textPressabilityProps={{
|
||||
@@ -278,57 +203,129 @@ const TextImpl: component(
|
||||
}}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
nativeText = (
|
||||
<NativeText
|
||||
{...restProps}
|
||||
accessibilityLabel={_accessibilityLabel}
|
||||
accessibilityState={_accessibilityState}
|
||||
accessible={_accessible}
|
||||
allowFontScaling={allowFontScaling !== false}
|
||||
disabled={_disabled}
|
||||
ellipsizeMode={ellipsizeMode ?? 'tail'}
|
||||
nativeID={_nativeID}
|
||||
numberOfLines={_numberOfLines}
|
||||
ref={forwardedRef}
|
||||
selectable={_selectable}
|
||||
selectionColor={_selectionColor}
|
||||
style={_style}>
|
||||
{children}
|
||||
</NativeText>
|
||||
);
|
||||
}
|
||||
|
||||
if (children == null) {
|
||||
return nativeText;
|
||||
}
|
||||
|
||||
// If the children do not contain a JSX element it would not be possible to have a
|
||||
// nested `Text` component so we can skip adding the `TextAncestor` context wrapper
|
||||
// which has a performance overhead. Since we do this for performance reasons we need
|
||||
// to keep the check simple to avoid regressing overall perf. For this reason the
|
||||
// `children.length` constant is set to `3`, this should be a reasonable tradeoff
|
||||
// to capture the majority of `Text` uses but also not make this check too expensive.
|
||||
if (Array.isArray(children) && children.length <= 3) {
|
||||
let hasNonTextChild = false;
|
||||
for (let child of children) {
|
||||
if (child != null && typeof child === 'object') {
|
||||
hasNonTextChild = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!hasNonTextChild) {
|
||||
return nativeText;
|
||||
}
|
||||
} else if (typeof children !== 'object') {
|
||||
return nativeText;
|
||||
}
|
||||
|
||||
return (
|
||||
<TextAncestor.Provider value={true}>{nativeText}</TextAncestor.Provider>
|
||||
<NativeVirtualText
|
||||
{...restProps}
|
||||
accessibilityLabel={_accessibilityLabel}
|
||||
accessibilityState={_accessibilityState}
|
||||
nativeID={_nativeID}
|
||||
numberOfLines={_numberOfLines}
|
||||
ref={forwardedRef}
|
||||
selectable={_selectable}
|
||||
selectionColor={_selectionColor}
|
||||
style={_style}
|
||||
disabled={disabled}>
|
||||
{children}
|
||||
</NativeVirtualText>
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// If the disabled prop and accessibilityState.disabled are out of sync but not both in
|
||||
// falsy states we need to update the accessibilityState object to use the disabled prop.
|
||||
if (
|
||||
_disabled !== _accessibilityStateDisabled &&
|
||||
((_disabled != null && _disabled !== false) ||
|
||||
(_accessibilityStateDisabled != null &&
|
||||
_accessibilityStateDisabled !== false))
|
||||
) {
|
||||
_accessibilityState = {..._accessibilityState, disabled: _disabled};
|
||||
}
|
||||
|
||||
const _accessible = Platform.select({
|
||||
ios: accessible !== false,
|
||||
android:
|
||||
accessible == null ? onPress != null || onLongPress != null : accessible,
|
||||
default: accessible,
|
||||
});
|
||||
|
||||
let nativeText = null;
|
||||
if (isPressable) {
|
||||
nativeText = (
|
||||
<NativePressableText
|
||||
ref={forwardedRef}
|
||||
textProps={{
|
||||
...restProps,
|
||||
accessibilityLabel: _accessibilityLabel,
|
||||
accessibilityState: _accessibilityState,
|
||||
accessible: _accessible,
|
||||
allowFontScaling: allowFontScaling !== false,
|
||||
disabled: _disabled,
|
||||
ellipsizeMode: ellipsizeMode ?? 'tail',
|
||||
nativeID: _nativeID,
|
||||
numberOfLines: _numberOfLines,
|
||||
selectable: _selectable,
|
||||
selectionColor: _selectionColor,
|
||||
style: _style,
|
||||
children,
|
||||
}}
|
||||
textPressabilityProps={{
|
||||
onLongPress,
|
||||
onPress,
|
||||
onPressIn,
|
||||
onPressOut,
|
||||
onResponderGrant,
|
||||
onResponderMove,
|
||||
onResponderRelease,
|
||||
onResponderTerminate,
|
||||
onResponderTerminationRequest,
|
||||
onStartShouldSetResponder,
|
||||
pressRetentionOffset,
|
||||
suppressHighlighting,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
nativeText = (
|
||||
<NativeText
|
||||
{...restProps}
|
||||
accessibilityLabel={_accessibilityLabel}
|
||||
accessibilityState={_accessibilityState}
|
||||
accessible={_accessible}
|
||||
allowFontScaling={allowFontScaling !== false}
|
||||
disabled={_disabled}
|
||||
ellipsizeMode={ellipsizeMode ?? 'tail'}
|
||||
nativeID={_nativeID}
|
||||
numberOfLines={_numberOfLines}
|
||||
ref={forwardedRef}
|
||||
selectable={_selectable}
|
||||
selectionColor={_selectionColor}
|
||||
style={_style}>
|
||||
{children}
|
||||
</NativeText>
|
||||
);
|
||||
}
|
||||
|
||||
if (children == null) {
|
||||
return nativeText;
|
||||
}
|
||||
|
||||
// If the children do not contain a JSX element it would not be possible to have a
|
||||
// nested `Text` component so we can skip adding the `TextAncestor` context wrapper
|
||||
// which has a performance overhead. Since we do this for performance reasons we need
|
||||
// to keep the check simple to avoid regressing overall perf. For this reason the
|
||||
// `children.length` constant is set to `3`, this should be a reasonable tradeoff
|
||||
// to capture the majority of `Text` uses but also not make this check too expensive.
|
||||
if (Array.isArray(children) && children.length <= 3) {
|
||||
let hasNonTextChild = false;
|
||||
for (let child of children) {
|
||||
if (child != null && typeof child === 'object') {
|
||||
hasNonTextChild = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!hasNonTextChild) {
|
||||
return nativeText;
|
||||
}
|
||||
} else if (typeof children !== 'object') {
|
||||
return nativeText;
|
||||
}
|
||||
|
||||
return (
|
||||
<TextAncestor.Provider value={true}>{nativeText}</TextAncestor.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
TextImpl.displayName = 'Text';
|
||||
|
||||
@@ -480,7 +477,14 @@ type NativePressableTextProps = $ReadOnly<{
|
||||
const NativePressableVirtualText: component(
|
||||
ref: React.RefSetter<TextForwardRef>,
|
||||
...props: NativePressableTextProps
|
||||
) = React.forwardRef(({textProps, textPressabilityProps}, forwardedRef) => {
|
||||
) = ({
|
||||
ref: forwardedRef,
|
||||
textProps,
|
||||
textPressabilityProps,
|
||||
}: {
|
||||
ref?: React.RefSetter<TextForwardRef>,
|
||||
...NativePressableTextProps,
|
||||
}) => {
|
||||
const [isHighlighted, eventHandlersForText] = useTextPressability(
|
||||
textPressabilityProps,
|
||||
);
|
||||
@@ -494,7 +498,7 @@ const NativePressableVirtualText: component(
|
||||
ref={forwardedRef}
|
||||
/>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Wrap the NativeText component and initialize pressability.
|
||||
@@ -505,7 +509,14 @@ const NativePressableVirtualText: component(
|
||||
const NativePressableText: component(
|
||||
ref: React.RefSetter<TextForwardRef>,
|
||||
...props: NativePressableTextProps
|
||||
) = React.forwardRef(({textProps, textPressabilityProps}, forwardedRef) => {
|
||||
) = ({
|
||||
ref: forwardedRef,
|
||||
textProps,
|
||||
textPressabilityProps,
|
||||
}: {
|
||||
ref?: React.RefSetter<TextForwardRef>,
|
||||
...NativePressableTextProps,
|
||||
}) => {
|
||||
const [isHighlighted, eventHandlersForText] = useTextPressability(
|
||||
textPressabilityProps,
|
||||
);
|
||||
@@ -519,7 +530,7 @@ const NativePressableText: component(
|
||||
ref={forwardedRef}
|
||||
/>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
const userSelectToSelectableMap = {
|
||||
auto: true,
|
||||
|
||||
Reference in New Issue
Block a user