@whetware/react-native-stroke-text 0.0.9 → 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.
|
@@ -3,6 +3,7 @@ package com.margelo.nitro.stroketext
|
|
|
3
3
|
import android.view.View
|
|
4
4
|
import com.facebook.jni.HybridData
|
|
5
5
|
import com.facebook.react.uimanager.ThemedReactContext
|
|
6
|
+
import kotlin.math.ceil
|
|
6
7
|
|
|
7
8
|
class HybridStrokeTextView(context: ThemedReactContext) : HybridStrokeTextViewSpec() {
|
|
8
9
|
private val strokeTextView = StrokeTextView(context)
|
|
@@ -77,12 +78,14 @@ class HybridStrokeTextView(context: ThemedReactContext) : HybridStrokeTextViewSp
|
|
|
77
78
|
)
|
|
78
79
|
|
|
79
80
|
strokeTextView.fontSizePx =
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
81
|
+
ceil(
|
|
82
|
+
StrokeTextView.textToPx(
|
|
83
|
+
fontSize ?: 14.0,
|
|
84
|
+
resolvedAllowFontScaling,
|
|
85
|
+
resolvedMaxFontSizeMultiplier,
|
|
86
|
+
displayMetrics,
|
|
87
|
+
).toDouble()
|
|
88
|
+
).toFloat()
|
|
86
89
|
strokeTextView.fontWeight = fontWeight
|
|
87
90
|
strokeTextView.fontFamily = fontFamily
|
|
88
91
|
strokeTextView.fontStyle = fontStyle ?: StrokeTextFontStyle.NORMAL
|
|
@@ -85,6 +85,8 @@ internal class StrokeTextView(context: ThemedReactContext) : TextView(context) {
|
|
|
85
85
|
val prevStrike = textPaint.isStrikeThruText
|
|
86
86
|
|
|
87
87
|
val saveCount = canvas.save()
|
|
88
|
+
val compoundPaddingLeft = compoundPaddingLeft
|
|
89
|
+
val extendedPaddingTop = extendedPaddingTop
|
|
88
90
|
|
|
89
91
|
canvas.translate(compoundPaddingLeft.toFloat(), extendedPaddingTop.toFloat())
|
|
90
92
|
canvas.translate(-scrollX.toFloat(), -scrollY.toFloat())
|
|
@@ -194,6 +196,10 @@ internal class StrokeTextView(context: ThemedReactContext) : TextView(context) {
|
|
|
194
196
|
|
|
195
197
|
// Line limits / ellipsizing
|
|
196
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)
|
|
197
203
|
setMaxLines(maxLines)
|
|
198
204
|
ellipsize =
|
|
199
205
|
if (numberOfLines <= 0) {
|
package/lib/StrokeText.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import {
|
|
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;
|
|
@@ -71,32 +87,12 @@ export function StrokeText({ text, children, style, hybridRef, ...rest }) {
|
|
|
71
87
|
const baseRight = firstNumber(paddingRight, stylePaddingRight, paddingHorizontal, stylePaddingHorizontal, padding, stylePadding) ?? 0;
|
|
72
88
|
const baseBottom = firstNumber(paddingBottom, stylePaddingBottom, paddingVertical, stylePaddingVertical, padding, stylePadding) ?? 0;
|
|
73
89
|
const baseLeft = firstNumber(paddingLeft, stylePaddingLeft, paddingHorizontal, stylePaddingHorizontal, padding, stylePadding) ?? 0;
|
|
74
|
-
const baseMarginTop = firstNumber(containerStyle.marginTop, containerStyle.marginVertical, containerStyle.margin) ?? 0;
|
|
75
|
-
const baseMarginRight = firstNumber(containerStyle.marginRight, I18nManager.isRTL ? containerStyle.marginStart : containerStyle.marginEnd, containerStyle.marginHorizontal, containerStyle.margin) ?? 0;
|
|
76
|
-
const baseMarginBottom = firstNumber(containerStyle.marginBottom, containerStyle.marginVertical, containerStyle.margin) ?? 0;
|
|
77
|
-
const baseMarginLeft = firstNumber(containerStyle.marginLeft, I18nManager.isRTL ? containerStyle.marginEnd : containerStyle.marginStart, containerStyle.marginHorizontal, containerStyle.margin) ?? 0;
|
|
78
|
-
const baseMarginStart = toNumber(containerStyle.marginStart);
|
|
79
|
-
const baseMarginEnd = toNumber(containerStyle.marginEnd);
|
|
80
|
-
const strokeInsetMarginStyle = strokeInset === 0
|
|
81
|
-
? null
|
|
82
|
-
: {
|
|
83
|
-
marginTop: baseMarginTop - strokeInset,
|
|
84
|
-
marginRight: baseMarginRight - strokeInset,
|
|
85
|
-
marginBottom: baseMarginBottom - strokeInset,
|
|
86
|
-
marginLeft: baseMarginLeft - strokeInset,
|
|
87
|
-
...(baseMarginStart == null
|
|
88
|
-
? {}
|
|
89
|
-
: { marginStart: baseMarginStart - strokeInset }),
|
|
90
|
-
...(baseMarginEnd == null
|
|
91
|
-
? {}
|
|
92
|
-
: { marginEnd: baseMarginEnd - strokeInset }),
|
|
93
|
-
};
|
|
94
90
|
const effectiveNumberOfLines = nativeProps.numberOfLines != null && nativeProps.numberOfLines > 0
|
|
95
91
|
? nativeProps.numberOfLines
|
|
96
92
|
: undefined;
|
|
97
93
|
const effectiveEllipsizeMode = effectiveNumberOfLines == null
|
|
98
94
|
? undefined
|
|
99
|
-
:
|
|
95
|
+
: nativeProps.ellipsizeMode ?? 'tail';
|
|
100
96
|
const mappedTextAlignVerticalFromVerticalAlign = styleVerticalAlign === 'middle'
|
|
101
97
|
? 'center'
|
|
102
98
|
: toTextAlignVertical(styleVerticalAlign);
|
|
@@ -114,7 +110,45 @@ export function StrokeText({ text, children, style, hybridRef, ...rest }) {
|
|
|
114
110
|
const effectiveTextDecorationLine = nativeProps.textDecorationLine ?? styleTextDecorationLine;
|
|
115
111
|
const effectiveTextTransform = nativeProps.textTransform ?? styleTextTransform;
|
|
116
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';
|
|
117
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]);
|
|
118
152
|
const measurerTextStyle = React.useMemo(() => ({
|
|
119
153
|
color: effectiveColor,
|
|
120
154
|
fontSize: effectiveFontSize,
|
|
@@ -142,9 +176,11 @@ export function StrokeText({ text, children, style, hybridRef, ...rest }) {
|
|
|
142
176
|
effectiveTextTransform,
|
|
143
177
|
effectiveIncludeFontPadding,
|
|
144
178
|
]);
|
|
145
|
-
return (React.createElement(View, { style: [styles.container, containerStyle
|
|
146
|
-
React.createElement(Text, { accessible: false, pointerEvents: "none", numberOfLines: effectiveNumberOfLines, ellipsizeMode: effectiveEllipsizeMode, allowFontScaling: nativeProps.allowFontScaling, maxFontSizeMultiplier: nativeProps.maxFontSizeMultiplier, style: [
|
|
179
|
+
return (React.createElement(View, { style: [styles.container, containerStyle] },
|
|
180
|
+
React.createElement(Text, { accessible: false, pointerEvents: "none", numberOfLines: effectiveNumberOfLines, ellipsizeMode: effectiveEllipsizeMode, onTextLayout: handleTextLayout, allowFontScaling: nativeProps.allowFontScaling, maxFontSizeMultiplier: nativeProps.maxFontSizeMultiplier, style: [
|
|
147
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.
|
|
148
184
|
{
|
|
149
185
|
paddingTop: baseTop + strokeInset,
|
|
150
186
|
paddingRight: baseRight + strokeInset,
|
|
@@ -153,7 +189,7 @@ export function StrokeText({ text, children, style, hybridRef, ...rest }) {
|
|
|
153
189
|
},
|
|
154
190
|
styles.hiddenText,
|
|
155
191
|
] }, resolvedText),
|
|
156
|
-
React.createElement(NativeStrokeTextView, { ...nativeProps, text:
|
|
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 })));
|
|
157
193
|
}
|
|
158
194
|
const styles = StyleSheet.create({
|
|
159
195
|
container: {},
|
package/package.json
CHANGED