@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: resolvedText, color: effectiveColor, fontSize: effectiveFontSize, fontWeight: effectiveFontWeight, fontFamily: effectiveFontFamily, fontStyle: effectiveFontStyle, lineHeight: effectiveLineHeight, letterSpacing: effectiveLetterSpacing, textAlign: effectiveTextAlign, textAlignVertical: effectiveTextAlignVertical, textDecorationLine: effectiveTextDecorationLine, textTransform: effectiveTextTransform, 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: [
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@whetware/react-native-stroke-text",
3
- "version": "0.0.10",
3
+ "version": "0.0.11",
4
4
  "description": "Stroke/outline text for React Native (New Architecture) on iOS, Android, and Web.",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",