@whetware/react-native-stroke-text 0.0.13 → 0.0.16

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @whetware/react-native-stroke-text
2
2
 
3
+ ## 0.0.15
4
+
5
+ ### Patch Changes
6
+
7
+ - [`57b743f`](https://github.com/whetware/react-native-stroke-text/commit/57b743f53c5ef6777be9564d38d2d95c5facb13b) Thanks [@evelant](https://github.com/evelant)! - Keep the native `textTransform` prop concrete during Android measured-line updates so Fabric never sends Nitro a null enum value.
8
+
3
9
  ## 0.0.13
4
10
 
5
11
  ### Patch Changes
package/lib/StrokeText.js CHANGED
@@ -2,6 +2,8 @@ import React from 'react';
2
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
+ import { createNativePropPresenceKey, normalizeNativeOptionalProps, } from './createNativePropPresenceKey';
6
+ import { resolveNativeTextTransform } from './resolveNativeTextTransform';
5
7
  const NativeStrokeTextView = getHostComponent('StrokeTextView', () => StrokeTextViewConfig);
6
8
  function resolveText(text, children) {
7
9
  if (typeof text === 'string')
@@ -113,7 +115,7 @@ export function StrokeText({ text, children, style, hybridRef, ...rest }) {
113
115
  const shouldSyncMeasuredLineBreaks = Platform.OS === 'android' && effectiveNumberOfLines == null;
114
116
  const [measuredNativeText, setMeasuredNativeText] = React.useState(undefined);
115
117
  const nativeText = measuredNativeText ?? resolvedText;
116
- const nativeTextTransform = measuredNativeText == null ? effectiveTextTransform : 'none';
118
+ const nativeTextTransform = resolveNativeTextTransform(effectiveTextTransform, measuredNativeText != null);
117
119
  // RN's hidden Text owns measurement, but Android TextView gets its own layout pass.
118
120
  // Compensate for the native stroke inset on every edge, then give that native
119
121
  // pass extra right-side room so one-pixel/font-metric drift cannot turn an RN
@@ -121,6 +123,39 @@ export function StrokeText({ text, children, style, hybridRef, ...rest }) {
121
123
  const nativeOverlayInset = Platform.OS === 'android' && strokeInset > 0 ? strokeInset : 0;
122
124
  const nativeOverlayRightInset = nativeOverlayInset > 0 ? nativeOverlayInset * 2 : 0;
123
125
  const wrappedHybridRef = React.useMemo(() => (hybridRef ? callback(hybridRef) : undefined), [hybridRef]);
126
+ const nativeOptionalProps = normalizeNativeOptionalProps({
127
+ color: effectiveColor,
128
+ strokeColor: nativeProps.strokeColor,
129
+ strokeWidth,
130
+ fontSize: effectiveFontSize,
131
+ fontWeight: effectiveFontWeight,
132
+ fontFamily: effectiveFontFamily,
133
+ fontStyle: effectiveFontStyle,
134
+ lineHeight: effectiveLineHeight,
135
+ letterSpacing: effectiveLetterSpacing,
136
+ textAlign: effectiveTextAlign,
137
+ textAlignVertical: effectiveTextAlignVertical,
138
+ textDecorationLine: effectiveTextDecorationLine,
139
+ textTransform: nativeTextTransform,
140
+ opacity: nativeProps.opacity ?? toNumber(styleOpacity),
141
+ allowFontScaling: nativeProps.allowFontScaling,
142
+ maxFontSizeMultiplier: nativeProps.maxFontSizeMultiplier,
143
+ includeFontPadding: effectiveIncludeFontPadding,
144
+ numberOfLines: effectiveNumberOfLines ?? 0,
145
+ ellipsizeMode: effectiveEllipsizeMode ?? 'tail',
146
+ padding: undefined,
147
+ paddingVertical: undefined,
148
+ paddingHorizontal: undefined,
149
+ paddingTop: baseTop,
150
+ paddingRight: baseRight,
151
+ paddingBottom: baseBottom,
152
+ paddingLeft: baseLeft,
153
+ hybridRef: wrappedHybridRef,
154
+ });
155
+ // Fabric encodes a removed view prop as null, but Nitro 0.36's optional
156
+ // converter accepts only undefined. Remount the native host when any optional
157
+ // prop changes definedness so Fabric never sends a value-to-null update.
158
+ const nativePropPresenceKey = createNativePropPresenceKey(nativeOptionalProps);
124
159
  React.useEffect(() => {
125
160
  setMeasuredNativeText(undefined);
126
161
  }, [
@@ -200,7 +235,7 @@ export function StrokeText({ text, children, style, hybridRef, ...rest }) {
200
235
  },
201
236
  styles.hiddenText,
202
237
  ] }, resolvedText),
203
- 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: [
238
+ React.createElement(NativeStrokeTextView, { key: nativePropPresenceKey, text: nativeText, ...nativeOptionalProps, pointerEvents: "none", style: [
204
239
  styles.overlay,
205
240
  nativeOverlayInset > 0
206
241
  ? {
@@ -0,0 +1,2 @@
1
+ export declare function normalizeNativeOptionalProps<T extends Readonly<Record<string, unknown>>>(props: T): T;
2
+ export declare function createNativePropPresenceKey(props: Readonly<Record<string, unknown>>): string;
@@ -0,0 +1,12 @@
1
+ export function normalizeNativeOptionalProps(props) {
2
+ return Object.fromEntries(Object.entries(props).map(([propName, value]) => [
3
+ propName,
4
+ value ?? undefined,
5
+ ]));
6
+ }
7
+ export function createNativePropPresenceKey(props) {
8
+ return Object.keys(props)
9
+ .sort()
10
+ .map((propName) => (props[propName] == null ? '0' : '1'))
11
+ .join('');
12
+ }
@@ -0,0 +1,7 @@
1
+ import type { StrokeTextTransform } from './specs/StrokeTextView.nitro';
2
+ /**
3
+ * Keep the Fabric host prop concrete across text-measurement state changes.
4
+ * React Native represents a removed prop as null during native updates, while
5
+ * Nitro's optional enum converter accepts undefined but rejects null.
6
+ */
7
+ export declare function resolveNativeTextTransform(effectiveTextTransform: StrokeTextTransform | undefined, hasMeasuredNativeText: boolean): StrokeTextTransform;
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Keep the Fabric host prop concrete across text-measurement state changes.
3
+ * React Native represents a removed prop as null during native updates, while
4
+ * Nitro's optional enum converter accepts undefined but rejects null.
5
+ */
6
+ export function resolveNativeTextTransform(effectiveTextTransform, hasMeasuredNativeText) {
7
+ return hasMeasuredNativeText ? 'none' : (effectiveTextTransform ?? 'none');
8
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@whetware/react-native-stroke-text",
3
- "version": "0.0.13",
3
+ "version": "0.0.16",
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",
@@ -33,6 +33,7 @@
33
33
  "clean": "rm -rf android/build node_modules/**/android/build lib",
34
34
  "lint": "eslint \"**/*.{js,ts,tsx}\" --fix",
35
35
  "lint-ci": "eslint \"**/*.{js,ts,tsx}\"",
36
+ "test": "tsc && node --test test/*.test.mjs",
36
37
  "typescript": "tsc",
37
38
  "prepare": "npm run prepack",
38
39
  "prepack": "tsc",
@@ -121,4 +122,4 @@
121
122
  "useTabs": false,
122
123
  "semi": false
123
124
  }
124
- }
125
+ }