jiuyiUniapp/service/node_modules/react-native/Libraries/Text/Text.js

538 lines
16 KiB
JavaScript

/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
*/
import type {TextStyleProp} from '../StyleSheet/StyleSheet';
import type {____TextStyle_Internal as TextStyleInternal} from '../StyleSheet/StyleSheetTypes';
import type {PressEvent} from '../Types/CoreEventTypes';
import type {NativeTextProps} from './TextNativeComponent';
import type {PressRetentionOffset, TextProps} from './TextProps';
import * as PressabilityDebug from '../Pressability/PressabilityDebug';
import usePressability from '../Pressability/usePressability';
import flattenStyle from '../StyleSheet/flattenStyle';
import processColor from '../StyleSheet/processColor';
import Platform from '../Utilities/Platform';
import TextAncestor from './TextAncestor';
import {NativeText, NativeVirtualText} from './TextNativeComponent';
import * as React from 'react';
import {useContext, useMemo, useState} from 'react';
type TextForwardRef = React.ElementRef<
typeof NativeText | typeof NativeVirtualText,
>;
/**
* Text is the fundamental component for displaying text.
*
* @see https://reactnative.dev/docs/text
*/
const Text: 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;
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 isPressable =
(onPress != null ||
onLongPress != null ||
onStartShouldSetResponder != null) &&
_disabled !== true;
// TODO: Move this processing to the view configuration.
const _selectionColor =
selectionColor != null ? processColor(selectionColor) : undefined;
let _style = style;
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>
);
}
// 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>
);
},
);
Text.displayName = 'Text';
type TextPressabilityProps = $ReadOnly<{
onLongPress?: ?(event: PressEvent) => mixed,
onPress?: ?(event: PressEvent) => mixed,
onPressIn?: ?(event: PressEvent) => mixed,
onPressOut?: ?(event: PressEvent) => mixed,
onResponderGrant?: ?(event: PressEvent) => void,
onResponderMove?: ?(event: PressEvent) => void,
onResponderRelease?: ?(event: PressEvent) => void,
onResponderTerminate?: ?(event: PressEvent) => void,
onResponderTerminationRequest?: ?() => boolean,
onStartShouldSetResponder?: ?() => boolean,
pressRetentionOffset?: ?PressRetentionOffset,
suppressHighlighting?: ?boolean,
}>;
/**
* Hook that handles setting up Pressability of Text components.
*
* NOTE: This hook is relatively expensive so it should only be used absolutely necessary.
*/
function useTextPressability({
onLongPress,
onPress,
onPressIn,
onPressOut,
onResponderGrant,
onResponderMove,
onResponderRelease,
onResponderTerminate,
onResponderTerminationRequest,
onStartShouldSetResponder,
pressRetentionOffset,
suppressHighlighting,
}: TextPressabilityProps) {
const [isHighlighted, setHighlighted] = useState(false);
// Setup pressability config and wrap callbacks needs to track the highlight state.
const config = useMemo(() => {
let _onPressIn = onPressIn;
let _onPressOut = onPressOut;
// Updating isHighlighted causes unnecessary re-renders for platforms that don't use it
// in the best case, and cause issues with text selection in the worst case. Forcing
// the isHighlighted prop to false on all platforms except iOS.
if (Platform.OS === 'ios') {
_onPressIn = (event: PressEvent) => {
setHighlighted(suppressHighlighting == null || !suppressHighlighting);
onPressIn?.(event);
};
_onPressOut = (event: PressEvent) => {
setHighlighted(false);
onPressOut?.(event);
};
}
return {
disabled: false,
pressRectOffset: pressRetentionOffset,
onLongPress,
onPress,
onPressIn: _onPressIn,
onPressOut: _onPressOut,
};
}, [
pressRetentionOffset,
onLongPress,
onPress,
onPressIn,
onPressOut,
suppressHighlighting,
]);
// Init the pressability class
const eventHandlers = usePressability(config);
// Create NativeText event handlers which proxy events to pressability
const eventHandlersForText = useMemo(
() =>
eventHandlers == null
? null
: {
onResponderGrant(event: PressEvent) {
eventHandlers.onResponderGrant(event);
if (onResponderGrant != null) {
onResponderGrant(event);
}
},
onResponderMove(event: PressEvent) {
eventHandlers.onResponderMove(event);
if (onResponderMove != null) {
onResponderMove(event);
}
},
onResponderRelease(event: PressEvent) {
eventHandlers.onResponderRelease(event);
if (onResponderRelease != null) {
onResponderRelease(event);
}
},
onResponderTerminate(event: PressEvent) {
eventHandlers.onResponderTerminate(event);
if (onResponderTerminate != null) {
onResponderTerminate(event);
}
},
onClick: eventHandlers.onClick,
onResponderTerminationRequest:
onResponderTerminationRequest != null
? onResponderTerminationRequest
: eventHandlers.onResponderTerminationRequest,
onStartShouldSetResponder:
onStartShouldSetResponder != null
? onStartShouldSetResponder
: eventHandlers.onStartShouldSetResponder,
},
[
eventHandlers,
onResponderGrant,
onResponderMove,
onResponderRelease,
onResponderTerminate,
onResponderTerminationRequest,
onStartShouldSetResponder,
],
);
// Return the highlight state and NativeText event handlers
return useMemo(
() => [isHighlighted, eventHandlersForText],
[isHighlighted, eventHandlersForText],
);
}
type NativePressableTextProps = $ReadOnly<{
textProps: NativeTextProps,
textPressabilityProps: TextPressabilityProps,
}>;
/**
* Wrap the NativeVirtualText component and initialize pressability.
*
* This logic is split out from the main Text component to enable the more
* expensive pressability logic to be only initialized when needed.
*/
const NativePressableVirtualText: component(
ref: React.RefSetter<TextForwardRef>,
...props: NativePressableTextProps
) = React.forwardRef(({textProps, textPressabilityProps}, forwardedRef) => {
const [isHighlighted, eventHandlersForText] = useTextPressability(
textPressabilityProps,
);
return (
<NativeVirtualText
{...textProps}
{...eventHandlersForText}
isHighlighted={isHighlighted}
isPressable={true}
ref={forwardedRef}
/>
);
});
/**
* Wrap the NativeText component and initialize pressability.
*
* This logic is split out from the main Text component to enable the more
* expensive pressability logic to be only initialized when needed.
*/
const NativePressableText: component(
ref: React.RefSetter<TextForwardRef>,
...props: NativePressableTextProps
) = React.forwardRef(({textProps, textPressabilityProps}, forwardedRef) => {
const [isHighlighted, eventHandlersForText] = useTextPressability(
textPressabilityProps,
);
return (
<NativeText
{...textProps}
{...eventHandlersForText}
isHighlighted={isHighlighted}
isPressable={true}
ref={forwardedRef}
/>
);
});
const userSelectToSelectableMap = {
auto: true,
text: true,
none: false,
contain: true,
all: true,
};
const verticalAlignToTextAlignVerticalMap = {
auto: 'auto',
top: 'top',
bottom: 'bottom',
middle: 'center',
};
module.exports = Text;