@whetware/react-native-stroke-text 0.0.10 → 0.0.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -196,6 +196,10 @@ internal class StrokeTextView(context: ThemedReactContext) : TextView(context) {
|
|
|
196
196
|
|
|
197
197
|
// Line limits / ellipsizing
|
|
198
198
|
val maxLines = if (numberOfLines > 0) numberOfLines else Int.MAX_VALUE
|
|
199
|
+
// React Native's single-line Text does not wrap at spaces. Without horizontal scrolling,
|
|
200
|
+
// Android TextView can create an internal second line inside a host view that was measured
|
|
201
|
+
// as one line by the hidden RN Text used for layout.
|
|
202
|
+
setHorizontallyScrolling(numberOfLines == 1)
|
|
199
203
|
setMaxLines(maxLines)
|
|
200
204
|
ellipsize =
|
|
201
205
|
if (numberOfLines <= 0) {
|
package/lib/StrokeText.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { StyleSheet, Text, View } from 'react-native';
|
|
2
|
+
import { Platform, StyleSheet, Text, View } from 'react-native';
|
|
3
3
|
import { callback, getHostComponent } from 'react-native-nitro-modules';
|
|
4
4
|
import StrokeTextViewConfig from '../nitrogen/generated/shared/json/StrokeTextViewConfig.json';
|
|
5
5
|
const NativeStrokeTextView = getHostComponent('StrokeTextView', () => StrokeTextViewConfig);
|
|
@@ -21,6 +21,22 @@ function firstNumber(...values) {
|
|
|
21
21
|
}
|
|
22
22
|
return undefined;
|
|
23
23
|
}
|
|
24
|
+
function toNativeMeasuredLineText(line) {
|
|
25
|
+
return line.text.replace(/ /g, '\u00a0');
|
|
26
|
+
}
|
|
27
|
+
function toNativeMeasuredText(lines) {
|
|
28
|
+
if (lines.length === 0)
|
|
29
|
+
return undefined;
|
|
30
|
+
let result = '';
|
|
31
|
+
lines.forEach((line, index) => {
|
|
32
|
+
const lineText = toNativeMeasuredLineText(line);
|
|
33
|
+
result += lineText;
|
|
34
|
+
if (index < lines.length - 1 && !lineText.endsWith('\n')) {
|
|
35
|
+
result += '\n';
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
return result;
|
|
39
|
+
}
|
|
24
40
|
function toFontWeightString(value) {
|
|
25
41
|
if (typeof value === 'string')
|
|
26
42
|
return value;
|
|
@@ -94,7 +110,45 @@ export function StrokeText({ text, children, style, hybridRef, ...rest }) {
|
|
|
94
110
|
const effectiveTextDecorationLine = nativeProps.textDecorationLine ?? styleTextDecorationLine;
|
|
95
111
|
const effectiveTextTransform = nativeProps.textTransform ?? styleTextTransform;
|
|
96
112
|
const effectiveColor = nativeProps.color ?? toColorString(styleColor);
|
|
113
|
+
const shouldSyncMeasuredLineBreaks = Platform.OS === 'android' && effectiveNumberOfLines == null;
|
|
114
|
+
const [measuredNativeText, setMeasuredNativeText] = React.useState(undefined);
|
|
115
|
+
const nativeText = measuredNativeText ?? resolvedText;
|
|
116
|
+
const nativeTextTransform = measuredNativeText == null ? effectiveTextTransform : 'none';
|
|
97
117
|
const wrappedHybridRef = React.useMemo(() => (hybridRef ? callback(hybridRef) : undefined), [hybridRef]);
|
|
118
|
+
React.useEffect(() => {
|
|
119
|
+
setMeasuredNativeText(undefined);
|
|
120
|
+
}, [
|
|
121
|
+
resolvedText,
|
|
122
|
+
effectiveNumberOfLines,
|
|
123
|
+
effectiveEllipsizeMode,
|
|
124
|
+
effectiveFontSize,
|
|
125
|
+
effectiveFontWeight,
|
|
126
|
+
effectiveFontFamily,
|
|
127
|
+
effectiveFontStyle,
|
|
128
|
+
effectiveLineHeight,
|
|
129
|
+
effectiveLetterSpacing,
|
|
130
|
+
effectiveTextAlign,
|
|
131
|
+
effectiveTextAlignVertical,
|
|
132
|
+
effectiveTextDecorationLine,
|
|
133
|
+
effectiveTextTransform,
|
|
134
|
+
effectiveIncludeFontPadding,
|
|
135
|
+
baseTop,
|
|
136
|
+
baseRight,
|
|
137
|
+
baseBottom,
|
|
138
|
+
baseLeft,
|
|
139
|
+
strokeInset,
|
|
140
|
+
]);
|
|
141
|
+
const handleTextLayout = React.useCallback((event) => {
|
|
142
|
+
if (!shouldSyncMeasuredLineBreaks)
|
|
143
|
+
return;
|
|
144
|
+
const nextText = toNativeMeasuredText(event.nativeEvent.lines);
|
|
145
|
+
if (nextText == null)
|
|
146
|
+
return;
|
|
147
|
+
const nextMeasuredNativeText = nextText === resolvedText ? undefined : nextText;
|
|
148
|
+
setMeasuredNativeText((currentText) => currentText === nextMeasuredNativeText
|
|
149
|
+
? currentText
|
|
150
|
+
: nextMeasuredNativeText);
|
|
151
|
+
}, [resolvedText, shouldSyncMeasuredLineBreaks]);
|
|
98
152
|
const measurerTextStyle = React.useMemo(() => ({
|
|
99
153
|
color: effectiveColor,
|
|
100
154
|
fontSize: effectiveFontSize,
|
|
@@ -123,27 +177,19 @@ export function StrokeText({ text, children, style, hybridRef, ...rest }) {
|
|
|
123
177
|
effectiveIncludeFontPadding,
|
|
124
178
|
]);
|
|
125
179
|
return (React.createElement(View, { style: [styles.container, containerStyle] },
|
|
126
|
-
React.createElement(Text, { accessible: false, pointerEvents: "none", numberOfLines: effectiveNumberOfLines, ellipsizeMode: effectiveEllipsizeMode, allowFontScaling: nativeProps.allowFontScaling, maxFontSizeMultiplier: nativeProps.maxFontSizeMultiplier, style: [
|
|
180
|
+
React.createElement(Text, { accessible: false, pointerEvents: "none", numberOfLines: effectiveNumberOfLines, ellipsizeMode: effectiveEllipsizeMode, onTextLayout: handleTextLayout, allowFontScaling: nativeProps.allowFontScaling, maxFontSizeMultiplier: nativeProps.maxFontSizeMultiplier, style: [
|
|
127
181
|
measurerTextStyle,
|
|
182
|
+
// The hidden RN Text is the layout source of truth, so reserve the same
|
|
183
|
+
// inset that the native TextView uses to keep the outline inside bounds.
|
|
128
184
|
{
|
|
129
|
-
paddingTop: baseTop,
|
|
130
|
-
paddingRight: baseRight,
|
|
131
|
-
paddingBottom: baseBottom,
|
|
132
|
-
paddingLeft: baseLeft,
|
|
185
|
+
paddingTop: baseTop + strokeInset,
|
|
186
|
+
paddingRight: baseRight + strokeInset,
|
|
187
|
+
paddingBottom: baseBottom + strokeInset,
|
|
188
|
+
paddingLeft: baseLeft + strokeInset,
|
|
133
189
|
},
|
|
134
190
|
styles.hiddenText,
|
|
135
191
|
] }, resolvedText),
|
|
136
|
-
React.createElement(NativeStrokeTextView, { ...nativeProps, text:
|
|
137
|
-
styles.overlay,
|
|
138
|
-
strokeInset === 0
|
|
139
|
-
? null
|
|
140
|
-
: {
|
|
141
|
-
top: -strokeInset,
|
|
142
|
-
right: -strokeInset,
|
|
143
|
-
bottom: -strokeInset,
|
|
144
|
-
left: -strokeInset,
|
|
145
|
-
},
|
|
146
|
-
] })));
|
|
192
|
+
React.createElement(NativeStrokeTextView, { ...nativeProps, text: nativeText, color: effectiveColor, fontSize: effectiveFontSize, fontWeight: effectiveFontWeight, fontFamily: effectiveFontFamily, fontStyle: effectiveFontStyle, lineHeight: effectiveLineHeight, letterSpacing: effectiveLetterSpacing, textAlign: effectiveTextAlign, textAlignVertical: effectiveTextAlignVertical, textDecorationLine: effectiveTextDecorationLine, textTransform: nativeTextTransform, opacity: nativeProps.opacity ?? toNumber(styleOpacity), includeFontPadding: effectiveIncludeFontPadding, numberOfLines: nativeProps.numberOfLines, ellipsizeMode: effectiveEllipsizeMode, paddingTop: baseTop, paddingRight: baseRight, paddingBottom: baseBottom, paddingLeft: baseLeft, hybridRef: wrappedHybridRef, pointerEvents: "none", style: styles.overlay })));
|
|
147
193
|
}
|
|
148
194
|
const styles = StyleSheet.create({
|
|
149
195
|
container: {},
|
package/package.json
CHANGED