From 7d01135cc044db340ce01010b9a998c6feb77fe4 Mon Sep 17 00:00:00 2001 From: huhuanming Date: Tue, 10 Mar 2026 21:58:10 +0800 Subject: [PATCH 1/8] feat(auto-size-input): support centered content layout - add contentCentered prop to keep prefix/input/suffix centered as a group - refine single-line auto-width sizing and baseline alignment on Android - regenerate nitrogen bindings/spec files for the new prop --- .../nitro/autosizeinput/AutoSizeInput.kt | 110 ++++++++++++++---- .../ios/AutoSizeInput.swift | 29 +++-- .../android/c++/JHybridAutoSizeInputSpec.cpp | 9 ++ .../android/c++/JHybridAutoSizeInputSpec.hpp | 2 + .../JHybridAutoSizeInputStateUpdater.cpp | 4 + .../autosizeinput/HybridAutoSizeInputSpec.kt | 6 + .../ios/c++/HybridAutoSizeInputSpecSwift.hpp | 7 ++ .../c++/views/HybridAutoSizeInputComponent.mm | 5 + .../ios/swift/HybridAutoSizeInputSpec.swift | 1 + .../swift/HybridAutoSizeInputSpec_cxx.swift | 24 ++++ .../shared/c++/HybridAutoSizeInputSpec.cpp | 2 + .../shared/c++/HybridAutoSizeInputSpec.hpp | 2 + .../views/HybridAutoSizeInputComponent.cpp | 12 ++ .../views/HybridAutoSizeInputComponent.hpp | 1 + .../shared/json/AutoSizeInputConfig.json | 1 + .../src/AutoSizeInput.nitro.ts | 8 +- 16 files changed, 193 insertions(+), 30 deletions(-) diff --git a/native-views/react-native-auto-size-input/android/src/main/java/com/margelo/nitro/autosizeinput/AutoSizeInput.kt b/native-views/react-native-auto-size-input/android/src/main/java/com/margelo/nitro/autosizeinput/AutoSizeInput.kt index 89fb52f..8d81843 100644 --- a/native-views/react-native-auto-size-input/android/src/main/java/com/margelo/nitro/autosizeinput/AutoSizeInput.kt +++ b/native-views/react-native-auto-size-input/android/src/main/java/com/margelo/nitro/autosizeinput/AutoSizeInput.kt @@ -342,6 +342,14 @@ class HybridAutoSizeInput(val context: ThemedReactContext) : HybridAutoSizeInput view.requestLayout() } + override var contentCentered: Boolean? = null + get() = field + set(value) { + if (isDisposed) return + field = value + view.requestLayout() + } + override var onChangeText: ((String) -> Unit)? = null override var onFocus: (() -> Unit)? = null @@ -436,24 +444,36 @@ class HybridAutoSizeInput(val context: ThemedReactContext) : HybridAutoSizeInput val prefixGap = if (prefixView.visibility == View.VISIBLE) ((prefixMarginRight ?: 0.0) * density).toInt() else 0 val suffixGap = if (suffixView.visibility == View.VISIBLE) ((suffixMarginLeft ?: 0.0) * density).toInt() else 0 - val inputX = edgeInset + prefixW + prefixGap val isContentAutoWidthEnabled = contentAutoWidth == true && multiline != true + val isContentCenteredEnabled = contentCentered == true && multiline != true + val prefixSegment = prefixW + prefixGap + val suffixSegment = if (suffixView.visibility == View.VISIBLE) suffixGap + suffixW else 0 + val availableTrackWidth = maxOf(width - (edgeInset * 2), 0) + val maxInputWidth = maxOf(availableTrackWidth - prefixSegment - suffixSegment, 0) val inputW: Int - val suffixX: Int if (isContentAutoWidthEnabled) { val typedText = inputView.text?.toString() ?: "" + val displayText = if (typedText.isEmpty()) (placeholder ?: "") else typedText val minInputWidth = (24f * density).toInt() - val desiredInputWidth = maxOf(measureSingleLineTextWidthPx(typedText), minInputWidth) - val suffixSegment = if (suffixView.visibility == View.VISIBLE) suffixGap + suffixW else 0 - val maxInputWidth = maxOf(width - edgeInset - inputX - suffixSegment, 0) + val singleLineWidthPadding = contentAutoWidthPaddingPx() + val desiredInputWidth = maxOf( + measureSingleLineTextWidthPx(displayText) + singleLineWidthPadding, + minInputWidth + ) inputW = minOf(desiredInputWidth, maxInputWidth) - val desiredSuffixX = if (suffixView.visibility == View.VISIBLE) inputX + inputW + suffixGap else width - edgeInset - suffixX = minOf(desiredSuffixX, width - edgeInset - suffixW) } else { - inputW = maxOf(width - edgeInset - inputX - suffixW - suffixGap, 0) - suffixX = width - edgeInset - suffixW + inputW = maxInputWidth + } + + val groupWidth = prefixSegment + inputW + suffixSegment + val groupStartX = if (isContentCenteredEnabled) { + edgeInset + maxOf((availableTrackWidth - groupWidth) / 2, 0) + } else { + edgeInset } + val inputX = groupStartX + prefixSegment + val suffixX = inputX + inputW + if (suffixView.visibility == View.VISIBLE) suffixGap else 0 // Re-measure with the exact final slot size before layout. if (prefixView.visibility == View.VISIBLE) { @@ -469,7 +489,6 @@ class HybridAutoSizeInput(val context: ThemedReactContext) : HybridAutoSizeInput ) } - // Layout input val inputHeightSpec = if (multiline == true) { View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY) } else { @@ -479,19 +498,51 @@ class HybridAutoSizeInput(val context: ThemedReactContext) : HybridAutoSizeInput View.MeasureSpec.makeMeasureSpec(inputW, View.MeasureSpec.EXACTLY), inputHeightSpec ) - val inputH = if (multiline == true) height else inputView.measuredHeight.coerceAtMost(height) - val inputTop = if (multiline == true) 0 else ((height - inputH) / 2).coerceAtLeast(0) + val inputH = if (multiline == true) { + height + } else { + inputView.measuredHeight.coerceAtMost(height) + } + val inputTop = if (multiline == true) { + 0 + } else { + ((height - inputH) / 2).coerceAtLeast(0) + } inputView.layout(inputX, inputTop, inputX + inputW, inputTop + inputH) resetSingleLineVerticalOffset() + val inputBaselineY = if (multiline == true) { + 0 + } else { + val baseline = inputView.baseline + if (baseline >= 0) inputTop + baseline else inputTop + (inputH / 2) + } - val prefixTop = ((height - prefixView.measuredHeight) / 2).coerceAtLeast(0) - val suffixTop = ((height - suffixView.measuredHeight) / 2).coerceAtLeast(0) + val prefixTop = if (multiline == true) { + ((height - prefixView.measuredHeight) / 2).coerceAtLeast(0) + } else { + topForBaseline(prefixView, inputBaselineY, height) + } + val suffixTop = if (multiline == true) { + ((height - suffixView.measuredHeight) / 2).coerceAtLeast(0) + } else { + topForBaseline(suffixView, inputBaselineY, height) + } // Layout prefix - prefixView.layout(edgeInset, prefixTop, edgeInset + prefixW, prefixTop + prefixView.measuredHeight) + prefixView.layout( + groupStartX, + prefixTop, + groupStartX + prefixW, + prefixTop + prefixView.measuredHeight + ) // Layout suffix - suffixView.layout(suffixX, suffixTop, suffixX + suffixW, suffixTop + suffixView.measuredHeight) + suffixView.layout( + suffixX, + suffixTop, + suffixX + suffixW, + suffixTop + suffixView.measuredHeight + ) } @@ -517,24 +568,27 @@ class HybridAutoSizeInput(val context: ThemedReactContext) : HybridAutoSizeInput if (isContentAutoWidthEnabled) { val density = context.resources.displayMetrics.density val edgeInset = (2f * density).toInt() + val singleLineWidthPadding = contentAutoWidthPaddingPx() val prefixW = if (prefixView.visibility == View.VISIBLE) measureTextViewWidthPx(prefixView) else 0 val suffixW = if (suffixView.visibility == View.VISIBLE) measureTextViewWidthPx(suffixView) else 0 val prefixGap = if (prefixView.visibility == View.VISIBLE) ((prefixMarginRight ?: 0.0) * density).toInt() else 0 val suffixGap = if (suffixView.visibility == View.VISIBLE) ((suffixMarginLeft ?: 0.0) * density).toInt() else 0 - val inputX = edgeInset + prefixW + prefixGap + val prefixSegment = prefixW + prefixGap val suffixSegment = if (suffixView.visibility == View.VISIBLE) suffixGap + suffixW else 0 - val maxInputWidth = maxOf(width - edgeInset - inputX - suffixSegment, 0) + val availableTrackWidth = maxOf(width - (edgeInset * 2), 0) + val maxInputWidth = maxOf(availableTrackWidth - prefixSegment - suffixSegment, 0) + val maxTextWidth = maxOf(maxInputWidth - singleLineWidthPadding, 0) val textForSizing = if (inputText.isEmpty()) (placeholder ?: "") else inputText // Expand width first; once width hits max, shrink font to keep full text visible. - val targetSize = if (maxInputWidth <= 0) { + val targetSize = if (maxTextWidth <= 0) { minSize } else if (textForSizing.isEmpty()) { maxSize } else { findOptimalFontSizeSingleLine( fullText = textForSizing, - availableWidth = maxInputWidth.toFloat(), + availableWidth = maxTextWidth.toFloat(), minSize = minSize, maxSize = maxSize ) @@ -739,6 +793,22 @@ class HybridAutoSizeInput(val context: ThemedReactContext) : HybridAutoSizeInput } } + private fun contentAutoWidthPaddingPx(): Int { + val density = context.resources.displayMetrics.density + return (8f * density).toInt() + } + + private fun topForBaseline(textView: TextView, targetBaseline: Int, containerHeight: Int): Int { + val measuredHeight = textView.measuredHeight + if (measuredHeight <= 0) return 0 + val baseline = textView.baseline + val fallback = ((containerHeight - measuredHeight) / 2).coerceAtLeast(0) + if (baseline < 0) return fallback + val rawTop = targetBaseline - baseline + val maxTop = maxOf(containerHeight - measuredHeight, 0) + return rawTop.coerceIn(0, maxTop) + } + override fun afterUpdate() { super.afterUpdate() if (!isDisposed) { diff --git a/native-views/react-native-auto-size-input/ios/AutoSizeInput.swift b/native-views/react-native-auto-size-input/ios/AutoSizeInput.swift index c745250..267fd7b 100644 --- a/native-views/react-native-auto-size-input/ios/AutoSizeInput.swift +++ b/native-views/react-native-auto-size-input/ios/AutoSizeInput.swift @@ -210,6 +210,12 @@ class HybridAutoSizeInput: HybridAutoSizeInputSpec { } } + var contentCentered: Bool? { + didSet { + view.setNeedsLayout() + } + } + var onChangeText: ((String) -> Void)? var onFocus: (() -> Void)? var onBlur: (() -> Void)? @@ -302,25 +308,30 @@ class HybridAutoSizeInput: HybridAutoSizeInputSpec { let prefixGap = prefixLabel.isHidden ? 0 : CGFloat(prefixMarginRight ?? 0) let suffixGap = suffixLabel.isHidden ? 0 : CGFloat(suffixMarginLeft ?? 0) - let inputX = prefixW + prefixGap let isContentAutoWidthEnabled = contentAutoWidth == true && multiline != true + let isContentCenteredEnabled = contentCentered == true && multiline != true + let prefixSegment = prefixW + prefixGap + let suffixSegment = suffixLabel.isHidden ? 0 : (suffixGap + suffixW) + let maxInputWidth = max(bounds.width - prefixSegment - suffixSegment, 0) let inputW: CGFloat - let suffixX: CGFloat if isContentAutoWidthEnabled { let typedText = singleLineInput.text ?? "" let desiredInputWidth = measuredSingleLineTextWidth(typedText, font: singleLineInput.font) - let suffixSegment = suffixLabel.isHidden ? 0 : (suffixGap + suffixW) - let maxInputWidth = max(bounds.width - inputX - suffixSegment, 0) inputW = min(desiredInputWidth, maxInputWidth) - let desiredSuffixX = suffixLabel.isHidden ? bounds.width : (inputX + inputW + suffixGap) - suffixX = min(desiredSuffixX, bounds.width - suffixW) } else { - inputW = max(bounds.width - inputX - suffixW - suffixGap, 0) - suffixX = bounds.width - suffixW + inputW = maxInputWidth } - prefixLabel.frame = CGRect(x: 0, y: 0, width: prefixW, height: bounds.height) + let groupWidth = prefixSegment + inputW + suffixSegment + let groupStartX = isContentCenteredEnabled + ? max((bounds.width - groupWidth) / 2, 0) + : 0 + let prefixX = groupStartX + let inputX = prefixX + prefixSegment + let suffixX = inputX + inputW + (suffixLabel.isHidden ? 0 : suffixGap) + + prefixLabel.frame = CGRect(x: prefixX, y: 0, width: prefixW, height: bounds.height) suffixLabel.frame = CGRect(x: suffixX, y: 0, width: suffixW, height: bounds.height) let activeInput: UIView = (multiline == true) ? multiLineInput : singleLineInput diff --git a/native-views/react-native-auto-size-input/nitrogen/generated/android/c++/JHybridAutoSizeInputSpec.cpp b/native-views/react-native-auto-size-input/nitrogen/generated/android/c++/JHybridAutoSizeInputSpec.cpp index 95bdaf8..b22b6d9 100644 --- a/native-views/react-native-auto-size-input/nitrogen/generated/android/c++/JHybridAutoSizeInputSpec.cpp +++ b/native-views/react-native-auto-size-input/nitrogen/generated/android/c++/JHybridAutoSizeInputSpec.cpp @@ -279,6 +279,15 @@ namespace margelo::nitro::autosizeinput { static const auto method = javaClassStatic()->getMethod /* contentAutoWidth */)>("setContentAutoWidth"); method(_javaPart, contentAutoWidth.has_value() ? jni::JBoolean::valueOf(contentAutoWidth.value()) : nullptr); } + std::optional JHybridAutoSizeInputSpec::getContentCentered() { + static const auto method = javaClassStatic()->getMethod()>("getContentCentered"); + auto __result = method(_javaPart); + return __result != nullptr ? std::make_optional(static_cast(__result->value())) : std::nullopt; + } + void JHybridAutoSizeInputSpec::setContentCentered(std::optional contentCentered) { + static const auto method = javaClassStatic()->getMethod /* contentCentered */)>("setContentCentered"); + method(_javaPart, contentCentered.has_value() ? jni::JBoolean::valueOf(contentCentered.value()) : nullptr); + } std::optional> JHybridAutoSizeInputSpec::getOnChangeText() { static const auto method = javaClassStatic()->getMethod()>("getOnChangeText_cxx"); auto __result = method(_javaPart); diff --git a/native-views/react-native-auto-size-input/nitrogen/generated/android/c++/JHybridAutoSizeInputSpec.hpp b/native-views/react-native-auto-size-input/nitrogen/generated/android/c++/JHybridAutoSizeInputSpec.hpp index 2ab627b..867c2a4 100644 --- a/native-views/react-native-auto-size-input/nitrogen/generated/android/c++/JHybridAutoSizeInputSpec.hpp +++ b/native-views/react-native-auto-size-input/nitrogen/generated/android/c++/JHybridAutoSizeInputSpec.hpp @@ -102,6 +102,8 @@ namespace margelo::nitro::autosizeinput { void setInputBackgroundColor(const std::optional& inputBackgroundColor) override; std::optional getContentAutoWidth() override; void setContentAutoWidth(std::optional contentAutoWidth) override; + std::optional getContentCentered() override; + void setContentCentered(std::optional contentCentered) override; std::optional> getOnChangeText() override; void setOnChangeText(const std::optional>& onChangeText) override; std::optional> getOnFocus() override; diff --git a/native-views/react-native-auto-size-input/nitrogen/generated/android/c++/views/JHybridAutoSizeInputStateUpdater.cpp b/native-views/react-native-auto-size-input/nitrogen/generated/android/c++/views/JHybridAutoSizeInputStateUpdater.cpp index 01fa7c7..79fca5c 100644 --- a/native-views/react-native-auto-size-input/nitrogen/generated/android/c++/views/JHybridAutoSizeInputStateUpdater.cpp +++ b/native-views/react-native-auto-size-input/nitrogen/generated/android/c++/views/JHybridAutoSizeInputStateUpdater.cpp @@ -140,6 +140,10 @@ void JHybridAutoSizeInputStateUpdater::updateViewProps(jni::alias_refsetContentAutoWidth(props.contentAutoWidth.value); // TODO: Set isDirty = false } + if (props.contentCentered.isDirty) { + view->setContentCentered(props.contentCentered.value); + // TODO: Set isDirty = false + } if (props.onChangeText.isDirty) { view->setOnChangeText(props.onChangeText.value); // TODO: Set isDirty = false diff --git a/native-views/react-native-auto-size-input/nitrogen/generated/android/kotlin/com/margelo/nitro/autosizeinput/HybridAutoSizeInputSpec.kt b/native-views/react-native-auto-size-input/nitrogen/generated/android/kotlin/com/margelo/nitro/autosizeinput/HybridAutoSizeInputSpec.kt index 8654473..43fd795 100644 --- a/native-views/react-native-auto-size-input/nitrogen/generated/android/kotlin/com/margelo/nitro/autosizeinput/HybridAutoSizeInputSpec.kt +++ b/native-views/react-native-auto-size-input/nitrogen/generated/android/kotlin/com/margelo/nitro/autosizeinput/HybridAutoSizeInputSpec.kt @@ -198,6 +198,12 @@ abstract class HybridAutoSizeInputSpec: HybridView() { @set:Keep abstract var contentAutoWidth: Boolean? + @get:DoNotStrip + @get:Keep + @set:DoNotStrip + @set:Keep + abstract var contentCentered: Boolean? + abstract var onChangeText: ((text: String) -> Unit)? private var onChangeText_cxx: Func_void_std__string? diff --git a/native-views/react-native-auto-size-input/nitrogen/generated/ios/c++/HybridAutoSizeInputSpecSwift.hpp b/native-views/react-native-auto-size-input/nitrogen/generated/ios/c++/HybridAutoSizeInputSpecSwift.hpp index 81b1ace..11cd396 100644 --- a/native-views/react-native-auto-size-input/nitrogen/generated/ios/c++/HybridAutoSizeInputSpecSwift.hpp +++ b/native-views/react-native-auto-size-input/nitrogen/generated/ios/c++/HybridAutoSizeInputSpecSwift.hpp @@ -240,6 +240,13 @@ namespace margelo::nitro::autosizeinput { inline void setContentAutoWidth(std::optional contentAutoWidth) noexcept override { _swiftPart.setContentAutoWidth(contentAutoWidth); } + inline std::optional getContentCentered() noexcept override { + auto __result = _swiftPart.getContentCentered(); + return __result; + } + inline void setContentCentered(std::optional contentCentered) noexcept override { + _swiftPart.setContentCentered(contentCentered); + } inline std::optional> getOnChangeText() noexcept override { auto __result = _swiftPart.getOnChangeText(); return __result; diff --git a/native-views/react-native-auto-size-input/nitrogen/generated/ios/c++/views/HybridAutoSizeInputComponent.mm b/native-views/react-native-auto-size-input/nitrogen/generated/ios/c++/views/HybridAutoSizeInputComponent.mm index e119976..f4d6bd6 100644 --- a/native-views/react-native-auto-size-input/nitrogen/generated/ios/c++/views/HybridAutoSizeInputComponent.mm +++ b/native-views/react-native-auto-size-input/nitrogen/generated/ios/c++/views/HybridAutoSizeInputComponent.mm @@ -201,6 +201,11 @@ - (void) updateProps:(const std::shared_ptr&)props swiftPart.setContentAutoWidth(newViewProps.contentAutoWidth.value); newViewProps.contentAutoWidth.isDirty = false; } + // contentCentered: optional + if (newViewProps.contentCentered.isDirty) { + swiftPart.setContentCentered(newViewProps.contentCentered.value); + newViewProps.contentCentered.isDirty = false; + } // onChangeText: optional if (newViewProps.onChangeText.isDirty) { swiftPart.setOnChangeText(newViewProps.onChangeText.value); diff --git a/native-views/react-native-auto-size-input/nitrogen/generated/ios/swift/HybridAutoSizeInputSpec.swift b/native-views/react-native-auto-size-input/nitrogen/generated/ios/swift/HybridAutoSizeInputSpec.swift index 6a5979d..43e1b8d 100644 --- a/native-views/react-native-auto-size-input/nitrogen/generated/ios/swift/HybridAutoSizeInputSpec.swift +++ b/native-views/react-native-auto-size-input/nitrogen/generated/ios/swift/HybridAutoSizeInputSpec.swift @@ -37,6 +37,7 @@ public protocol HybridAutoSizeInputSpec_protocol: HybridObject, HybridView { var showBorder: Bool? { get set } var inputBackgroundColor: String? { get set } var contentAutoWidth: Bool? { get set } + var contentCentered: Bool? { get set } var onChangeText: ((_ text: String) -> Void)? { get set } var onFocus: (() -> Void)? { get set } var onBlur: (() -> Void)? { get set } diff --git a/native-views/react-native-auto-size-input/nitrogen/generated/ios/swift/HybridAutoSizeInputSpec_cxx.swift b/native-views/react-native-auto-size-input/nitrogen/generated/ios/swift/HybridAutoSizeInputSpec_cxx.swift index 59f4ca8..bbeec8d 100644 --- a/native-views/react-native-auto-size-input/nitrogen/generated/ios/swift/HybridAutoSizeInputSpec_cxx.swift +++ b/native-views/react-native-auto-size-input/nitrogen/generated/ios/swift/HybridAutoSizeInputSpec_cxx.swift @@ -703,6 +703,30 @@ open class HybridAutoSizeInputSpec_cxx { } } + public final var contentCentered: bridge.std__optional_bool_ { + @inline(__always) + get { + return { () -> bridge.std__optional_bool_ in + if let __unwrappedValue = self.__implementation.contentCentered { + return bridge.create_std__optional_bool_(__unwrappedValue) + } else { + return .init() + } + }() + } + @inline(__always) + set { + self.__implementation.contentCentered = { () -> Bool? in + if bridge.has_value_std__optional_bool_(newValue) { + let __unwrapped = bridge.get_std__optional_bool_(newValue) + return __unwrapped + } else { + return nil + } + }() + } + } + public final var onChangeText: bridge.std__optional_std__function_void_const_std__string_____text______ { @inline(__always) get { diff --git a/native-views/react-native-auto-size-input/nitrogen/generated/shared/c++/HybridAutoSizeInputSpec.cpp b/native-views/react-native-auto-size-input/nitrogen/generated/shared/c++/HybridAutoSizeInputSpec.cpp index aef81ae..5ca7b68 100644 --- a/native-views/react-native-auto-size-input/nitrogen/generated/shared/c++/HybridAutoSizeInputSpec.cpp +++ b/native-views/react-native-auto-size-input/nitrogen/generated/shared/c++/HybridAutoSizeInputSpec.cpp @@ -66,6 +66,8 @@ namespace margelo::nitro::autosizeinput { prototype.registerHybridSetter("inputBackgroundColor", &HybridAutoSizeInputSpec::setInputBackgroundColor); prototype.registerHybridGetter("contentAutoWidth", &HybridAutoSizeInputSpec::getContentAutoWidth); prototype.registerHybridSetter("contentAutoWidth", &HybridAutoSizeInputSpec::setContentAutoWidth); + prototype.registerHybridGetter("contentCentered", &HybridAutoSizeInputSpec::getContentCentered); + prototype.registerHybridSetter("contentCentered", &HybridAutoSizeInputSpec::setContentCentered); prototype.registerHybridGetter("onChangeText", &HybridAutoSizeInputSpec::getOnChangeText); prototype.registerHybridSetter("onChangeText", &HybridAutoSizeInputSpec::setOnChangeText); prototype.registerHybridGetter("onFocus", &HybridAutoSizeInputSpec::getOnFocus); diff --git a/native-views/react-native-auto-size-input/nitrogen/generated/shared/c++/HybridAutoSizeInputSpec.hpp b/native-views/react-native-auto-size-input/nitrogen/generated/shared/c++/HybridAutoSizeInputSpec.hpp index 041f88f..4e29d93 100644 --- a/native-views/react-native-auto-size-input/nitrogen/generated/shared/c++/HybridAutoSizeInputSpec.hpp +++ b/native-views/react-native-auto-size-input/nitrogen/generated/shared/c++/HybridAutoSizeInputSpec.hpp @@ -98,6 +98,8 @@ namespace margelo::nitro::autosizeinput { virtual void setInputBackgroundColor(const std::optional& inputBackgroundColor) = 0; virtual std::optional getContentAutoWidth() = 0; virtual void setContentAutoWidth(std::optional contentAutoWidth) = 0; + virtual std::optional getContentCentered() = 0; + virtual void setContentCentered(std::optional contentCentered) = 0; virtual std::optional> getOnChangeText() = 0; virtual void setOnChangeText(const std::optional>& onChangeText) = 0; virtual std::optional> getOnFocus() = 0; diff --git a/native-views/react-native-auto-size-input/nitrogen/generated/shared/c++/views/HybridAutoSizeInputComponent.cpp b/native-views/react-native-auto-size-input/nitrogen/generated/shared/c++/views/HybridAutoSizeInputComponent.cpp index dc3c924..21ad2e0 100644 --- a/native-views/react-native-auto-size-input/nitrogen/generated/shared/c++/views/HybridAutoSizeInputComponent.cpp +++ b/native-views/react-native-auto-size-input/nitrogen/generated/shared/c++/views/HybridAutoSizeInputComponent.cpp @@ -285,6 +285,16 @@ namespace margelo::nitro::autosizeinput::views { throw std::runtime_error(std::string("AutoSizeInput.contentAutoWidth: ") + exc.what()); } }()), + contentCentered([&]() -> CachedProp> { + try { + const react::RawValue* rawValue = rawProps.at("contentCentered", nullptr, nullptr); + if (rawValue == nullptr) return sourceProps.contentCentered; + const auto& [runtime, value] = (std::pair)*rawValue; + return CachedProp>::fromRawValue(*runtime, value, sourceProps.contentCentered); + } catch (const std::exception& exc) { + throw std::runtime_error(std::string("AutoSizeInput.contentCentered: ") + exc.what()); + } + }()), onChangeText([&]() -> CachedProp>> { try { const react::RawValue* rawValue = rawProps.at("onChangeText", nullptr, nullptr); @@ -354,6 +364,7 @@ namespace margelo::nitro::autosizeinput::views { showBorder(other.showBorder), inputBackgroundColor(other.inputBackgroundColor), contentAutoWidth(other.contentAutoWidth), + contentCentered(other.contentCentered), onChangeText(other.onChangeText), onFocus(other.onFocus), onBlur(other.onBlur), @@ -387,6 +398,7 @@ namespace margelo::nitro::autosizeinput::views { case hashString("showBorder"): return true; case hashString("inputBackgroundColor"): return true; case hashString("contentAutoWidth"): return true; + case hashString("contentCentered"): return true; case hashString("onChangeText"): return true; case hashString("onFocus"): return true; case hashString("onBlur"): return true; diff --git a/native-views/react-native-auto-size-input/nitrogen/generated/shared/c++/views/HybridAutoSizeInputComponent.hpp b/native-views/react-native-auto-size-input/nitrogen/generated/shared/c++/views/HybridAutoSizeInputComponent.hpp index efa2fab..e4c8f15 100644 --- a/native-views/react-native-auto-size-input/nitrogen/generated/shared/c++/views/HybridAutoSizeInputComponent.hpp +++ b/native-views/react-native-auto-size-input/nitrogen/generated/shared/c++/views/HybridAutoSizeInputComponent.hpp @@ -69,6 +69,7 @@ namespace margelo::nitro::autosizeinput::views { CachedProp> showBorder; CachedProp> inputBackgroundColor; CachedProp> contentAutoWidth; + CachedProp> contentCentered; CachedProp>> onChangeText; CachedProp>> onFocus; CachedProp>> onBlur; diff --git a/native-views/react-native-auto-size-input/nitrogen/generated/shared/json/AutoSizeInputConfig.json b/native-views/react-native-auto-size-input/nitrogen/generated/shared/json/AutoSizeInputConfig.json index cd88ca1..261adfa 100644 --- a/native-views/react-native-auto-size-input/nitrogen/generated/shared/json/AutoSizeInputConfig.json +++ b/native-views/react-native-auto-size-input/nitrogen/generated/shared/json/AutoSizeInputConfig.json @@ -30,6 +30,7 @@ "showBorder": true, "inputBackgroundColor": true, "contentAutoWidth": true, + "contentCentered": true, "onChangeText": true, "onFocus": true, "onBlur": true, diff --git a/native-views/react-native-auto-size-input/src/AutoSizeInput.nitro.ts b/native-views/react-native-auto-size-input/src/AutoSizeInput.nitro.ts index 5b68650..f1e93c0 100644 --- a/native-views/react-native-auto-size-input/src/AutoSizeInput.nitro.ts +++ b/native-views/react-native-auto-size-input/src/AutoSizeInput.nitro.ts @@ -49,6 +49,9 @@ export interface AutoSizeInputProps extends HybridViewProps { // Let input width grow with content and push suffix to the right contentAutoWidth?: boolean; + // Keep prefix + input + suffix centered as a whole inside the container + contentCentered?: boolean; + // Event callbacks onChangeText?: (text: string) => void; onFocus?: () => void; @@ -60,4 +63,7 @@ export interface AutoSizeInputMethods extends HybridViewMethods { blur(): void; } -export type AutoSizeInput = HybridView; +export type AutoSizeInput = HybridView< + AutoSizeInputProps, + AutoSizeInputMethods +>; From 50bd0ddd7ad2575512008bd2b321176e5d96b44c Mon Sep 17 00:00:00 2001 From: huhuanming Date: Tue, 10 Mar 2026 22:00:07 +0800 Subject: [PATCH 2/8] fix(auto-size-input): improve baseline centering on android - compute a centered target baseline from current font metrics - align input/prefix/suffix with shared baseline in single-line layout --- .../nitro/autosizeinput/AutoSizeInput.kt | 26 ++++++++++++------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/native-views/react-native-auto-size-input/android/src/main/java/com/margelo/nitro/autosizeinput/AutoSizeInput.kt b/native-views/react-native-auto-size-input/android/src/main/java/com/margelo/nitro/autosizeinput/AutoSizeInput.kt index 8d81843..d5a1b7b 100644 --- a/native-views/react-native-auto-size-input/android/src/main/java/com/margelo/nitro/autosizeinput/AutoSizeInput.kt +++ b/native-views/react-native-auto-size-input/android/src/main/java/com/margelo/nitro/autosizeinput/AutoSizeInput.kt @@ -503,29 +503,28 @@ class HybridAutoSizeInput(val context: ThemedReactContext) : HybridAutoSizeInput } else { inputView.measuredHeight.coerceAtMost(height) } - val inputTop = if (multiline == true) { + val targetBaseline: Int = if (multiline == true) { 0 } else { - ((height - inputH) / 2).coerceAtLeast(0) + centeredBaselineY(height) } - inputView.layout(inputX, inputTop, inputX + inputW, inputTop + inputH) - resetSingleLineVerticalOffset() - val inputBaselineY = if (multiline == true) { + val inputTop = if (multiline == true) { 0 } else { - val baseline = inputView.baseline - if (baseline >= 0) inputTop + baseline else inputTop + (inputH / 2) + topForBaseline(inputView, targetBaseline, height) } + inputView.layout(inputX, inputTop, inputX + inputW, inputTop + inputH) + resetSingleLineVerticalOffset() val prefixTop = if (multiline == true) { ((height - prefixView.measuredHeight) / 2).coerceAtLeast(0) } else { - topForBaseline(prefixView, inputBaselineY, height) + topForBaseline(prefixView, targetBaseline, height) } val suffixTop = if (multiline == true) { ((height - suffixView.measuredHeight) / 2).coerceAtLeast(0) } else { - topForBaseline(suffixView, inputBaselineY, height) + topForBaseline(suffixView, targetBaseline, height) } // Layout prefix @@ -798,6 +797,15 @@ class HybridAutoSizeInput(val context: ThemedReactContext) : HybridAutoSizeInput return (8f * density).toInt() } + private fun centeredBaselineY(containerHeight: Int): Int { + val paint = TextPaint(Paint.ANTI_ALIAS_FLAG) + paint.textSize = currentFontSize * context.resources.displayMetrics.scaledDensity + paint.typeface = makeTypeface() + return kotlin.math.round( + (containerHeight / 2f) - ((paint.descent() + paint.ascent()) / 2f) + ).toInt() + } + private fun topForBaseline(textView: TextView, targetBaseline: Int, containerHeight: Int): Int { val measuredHeight = textView.measuredHeight if (measuredHeight <= 0) return 0 From 1f6316c4e224f2b73c8006c613c2c72f061dcdd2 Mon Sep 17 00:00:00 2001 From: huhuanming Date: Tue, 10 Mar 2026 22:18:17 +0800 Subject: [PATCH 3/8] Update AutoSizeInput.kt --- .../com/margelo/nitro/autosizeinput/AutoSizeInput.kt | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/native-views/react-native-auto-size-input/android/src/main/java/com/margelo/nitro/autosizeinput/AutoSizeInput.kt b/native-views/react-native-auto-size-input/android/src/main/java/com/margelo/nitro/autosizeinput/AutoSizeInput.kt index d5a1b7b..04b8746 100644 --- a/native-views/react-native-auto-size-input/android/src/main/java/com/margelo/nitro/autosizeinput/AutoSizeInput.kt +++ b/native-views/react-native-auto-size-input/android/src/main/java/com/margelo/nitro/autosizeinput/AutoSizeInput.kt @@ -450,15 +450,15 @@ class HybridAutoSizeInput(val context: ThemedReactContext) : HybridAutoSizeInput val suffixSegment = if (suffixView.visibility == View.VISIBLE) suffixGap + suffixW else 0 val availableTrackWidth = maxOf(width - (edgeInset * 2), 0) val maxInputWidth = maxOf(availableTrackWidth - prefixSegment - suffixSegment, 0) + val rawInputText = inputView.text?.toString() ?: "" + val displayInputText = if (rawInputText.isEmpty()) (placeholder ?: "") else rawInputText val inputW: Int if (isContentAutoWidthEnabled) { - val typedText = inputView.text?.toString() ?: "" - val displayText = if (typedText.isEmpty()) (placeholder ?: "") else typedText val minInputWidth = (24f * density).toInt() val singleLineWidthPadding = contentAutoWidthPaddingPx() val desiredInputWidth = maxOf( - measureSingleLineTextWidthPx(displayText) + singleLineWidthPadding, + measureSingleLineTextWidthPx(displayInputText) + singleLineWidthPadding, minInputWidth ) inputW = minOf(desiredInputWidth, maxInputWidth) @@ -798,9 +798,7 @@ class HybridAutoSizeInput(val context: ThemedReactContext) : HybridAutoSizeInput } private fun centeredBaselineY(containerHeight: Int): Int { - val paint = TextPaint(Paint.ANTI_ALIAS_FLAG) - paint.textSize = currentFontSize * context.resources.displayMetrics.scaledDensity - paint.typeface = makeTypeface() + val paint = inputView.paint return kotlin.math.round( (containerHeight / 2f) - ((paint.descent() + paint.ascent()) / 2f) ).toInt() From a52db245d7e351688817dd1d16bc9332eaf106b7 Mon Sep 17 00:00:00 2001 From: huhuanming Date: Tue, 10 Mar 2026 22:35:38 +0800 Subject: [PATCH 4/8] Update AutoSizeInput.kt --- .../nitro/autosizeinput/AutoSizeInput.kt | 46 +++++++++++++++++-- 1 file changed, 41 insertions(+), 5 deletions(-) diff --git a/native-views/react-native-auto-size-input/android/src/main/java/com/margelo/nitro/autosizeinput/AutoSizeInput.kt b/native-views/react-native-auto-size-input/android/src/main/java/com/margelo/nitro/autosizeinput/AutoSizeInput.kt index 04b8746..621bbc4 100644 --- a/native-views/react-native-auto-size-input/android/src/main/java/com/margelo/nitro/autosizeinput/AutoSizeInput.kt +++ b/native-views/react-native-auto-size-input/android/src/main/java/com/margelo/nitro/autosizeinput/AutoSizeInput.kt @@ -577,21 +577,31 @@ class HybridAutoSizeInput(val context: ThemedReactContext) : HybridAutoSizeInput val availableTrackWidth = maxOf(width - (edgeInset * 2), 0) val maxInputWidth = maxOf(availableTrackWidth - prefixSegment - suffixSegment, 0) val maxTextWidth = maxOf(maxInputWidth - singleLineWidthPadding, 0) + val maxTextHeight = maxOf(height - inputView.paddingTop - inputView.paddingBottom, 0) val textForSizing = if (inputText.isEmpty()) (placeholder ?: "") else inputText + val probeText = if (textForSizing.isEmpty()) "0" else textForSizing - // Expand width first; once width hits max, shrink font to keep full text visible. - val targetSize = if (maxTextWidth <= 0) { + // Keep both width and line-height within the input slot. + val widthFitSize = if (maxTextWidth <= 0) { minSize - } else if (textForSizing.isEmpty()) { - maxSize } else { findOptimalFontSizeSingleLine( - fullText = textForSizing, + fullText = probeText, availableWidth = maxTextWidth.toFloat(), minSize = minSize, maxSize = maxSize ) } + val heightFitSize = if (maxTextHeight <= 0) { + minSize + } else { + findOptimalFontSizeForSingleLineHeight( + availableHeight = maxTextHeight.toFloat(), + minSize = minSize, + maxSize = maxSize + ) + } + val targetSize = minOf(widthFitSize, heightFitSize).coerceIn(minSize, maxSize) applyFontSize(targetSize) return } @@ -681,6 +691,32 @@ class HybridAutoSizeInput(val context: ThemedReactContext) : HybridAutoSizeInput return low } + private fun findOptimalFontSizeForSingleLineHeight( + availableHeight: Float, + minSize: Float, + maxSize: Float + ): Float { + if (availableHeight <= 0f) return minSize + var low = minSize + var high = maxSize + val paint = TextPaint(Paint.ANTI_ALIAS_FLAG) + paint.typeface = makeTypeface() + + while (high - low > 0.5f) { + val mid = (low + high) / 2f + paint.textSize = mid * context.resources.displayMetrics.scaledDensity + val fm = paint.fontMetrics + val lineHeight = kotlin.math.ceil((fm.descent - fm.ascent).toDouble()).toFloat() + if (lineHeight <= availableHeight) { + low = mid + } else { + high = mid + } + } + + return low + } + private fun applyFontSize(size: Float) { currentFontSize = size val typeface = makeTypeface() From 1dcd11112ec45e1bd2c1e7a0d0a128c5408fee3c Mon Sep 17 00:00:00 2001 From: huhuanming Date: Tue, 10 Mar 2026 23:41:31 +0800 Subject: [PATCH 5/8] fix(auto-size-input): align iOS auto-width sizing with Android - use placeholder text as fallback when input is empty - apply min width and width padding in contentAutoWidth layout --- .../ios/AutoSizeInput.swift | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/native-views/react-native-auto-size-input/ios/AutoSizeInput.swift b/native-views/react-native-auto-size-input/ios/AutoSizeInput.swift index 267fd7b..f700450 100644 --- a/native-views/react-native-auto-size-input/ios/AutoSizeInput.swift +++ b/native-views/react-native-auto-size-input/ios/AutoSizeInput.swift @@ -317,7 +317,12 @@ class HybridAutoSizeInput: HybridAutoSizeInputSpec { if isContentAutoWidthEnabled { let typedText = singleLineInput.text ?? "" - let desiredInputWidth = measuredSingleLineTextWidth(typedText, font: singleLineInput.font) + let displayText = typedText.isEmpty ? (placeholder ?? "") : typedText + let minInputWidth: CGFloat = 24 + let desiredInputWidth = max( + measuredSingleLineTextWidth(displayText, font: singleLineInput.font) + contentAutoWidthPadding(), + minInputWidth + ) inputW = min(desiredInputWidth, maxInputWidth) } else { inputW = maxInputWidth @@ -600,6 +605,10 @@ class HybridAutoSizeInput: HybridAutoSizeInputSpec { return (text as NSString).size(withAttributes: [.font: effectiveFont]).width } + private func contentAutoWidthPadding() -> CGFloat { + return 8 + } + private func textAlignmentFrom(_ align: String?) -> NSTextAlignment { switch align { case "center": return .center From cd8451725b815c2a167e2cdc60257dc207e685f8 Mon Sep 17 00:00:00 2001 From: huhuanming Date: Tue, 10 Mar 2026 23:46:07 +0800 Subject: [PATCH 6/8] 1.1.36 --- native-modules/native-logger/package.json | 2 +- native-modules/react-native-app-update/package.json | 2 +- native-modules/react-native-background-thread/package.json | 2 +- native-modules/react-native-bundle-update/package.json | 2 +- .../react-native-check-biometric-auth-changed/package.json | 2 +- native-modules/react-native-cloud-kit-module/package.json | 2 +- native-modules/react-native-device-utils/package.json | 2 +- native-modules/react-native-get-random-values/package.json | 2 +- native-modules/react-native-keychain-module/package.json | 2 +- native-modules/react-native-lite-card/package.json | 2 +- native-modules/react-native-perf-memory/package.json | 2 +- native-modules/react-native-splash-screen/package.json | 2 +- native-views/react-native-auto-size-input/package.json | 2 +- native-views/react-native-pager-view/package.json | 2 +- native-views/react-native-skeleton/package.json | 2 +- native-views/react-native-tab-view/package.json | 2 +- 16 files changed, 16 insertions(+), 16 deletions(-) diff --git a/native-modules/native-logger/package.json b/native-modules/native-logger/package.json index 7623285..bc8c081 100644 --- a/native-modules/native-logger/package.json +++ b/native-modules/native-logger/package.json @@ -1,6 +1,6 @@ { "name": "@onekeyfe/react-native-native-logger", - "version": "1.1.35", + "version": "1.1.36", "description": "react-native-native-logger", "main": "./lib/module/index.js", "types": "./lib/typescript/src/index.d.ts", diff --git a/native-modules/react-native-app-update/package.json b/native-modules/react-native-app-update/package.json index 99284c1..712c6f1 100644 --- a/native-modules/react-native-app-update/package.json +++ b/native-modules/react-native-app-update/package.json @@ -1,6 +1,6 @@ { "name": "@onekeyfe/react-native-app-update", - "version": "1.1.35", + "version": "1.1.36", "description": "react-native-app-update", "main": "./lib/module/index.js", "types": "./lib/typescript/src/index.d.ts", diff --git a/native-modules/react-native-background-thread/package.json b/native-modules/react-native-background-thread/package.json index 806b194..32ba0b3 100644 --- a/native-modules/react-native-background-thread/package.json +++ b/native-modules/react-native-background-thread/package.json @@ -1,6 +1,6 @@ { "name": "@onekeyfe/react-native-background-thread", - "version": "1.1.35", + "version": "1.1.36", "description": "react-native-background-thread", "main": "./lib/module/index.js", "types": "./lib/typescript/src/index.d.ts", diff --git a/native-modules/react-native-bundle-update/package.json b/native-modules/react-native-bundle-update/package.json index bef6749..8f28288 100644 --- a/native-modules/react-native-bundle-update/package.json +++ b/native-modules/react-native-bundle-update/package.json @@ -1,6 +1,6 @@ { "name": "@onekeyfe/react-native-bundle-update", - "version": "1.1.35", + "version": "1.1.36", "description": "react-native-bundle-update", "main": "./lib/module/index.js", "types": "./lib/typescript/src/index.d.ts", diff --git a/native-modules/react-native-check-biometric-auth-changed/package.json b/native-modules/react-native-check-biometric-auth-changed/package.json index 235b8de..8b2b6b1 100644 --- a/native-modules/react-native-check-biometric-auth-changed/package.json +++ b/native-modules/react-native-check-biometric-auth-changed/package.json @@ -1,6 +1,6 @@ { "name": "@onekeyfe/react-native-check-biometric-auth-changed", - "version": "1.1.35", + "version": "1.1.36", "description": "react-native-check-biometric-auth-changed", "main": "./lib/module/index.js", "types": "./lib/typescript/src/index.d.ts", diff --git a/native-modules/react-native-cloud-kit-module/package.json b/native-modules/react-native-cloud-kit-module/package.json index 8a95fd0..3a3c1d8 100644 --- a/native-modules/react-native-cloud-kit-module/package.json +++ b/native-modules/react-native-cloud-kit-module/package.json @@ -1,6 +1,6 @@ { "name": "@onekeyfe/react-native-cloud-kit-module", - "version": "1.1.35", + "version": "1.1.36", "description": "react-native-cloud-kit-module", "main": "./lib/module/index.js", "types": "./lib/typescript/src/index.d.ts", diff --git a/native-modules/react-native-device-utils/package.json b/native-modules/react-native-device-utils/package.json index 6bcc190..281f88c 100644 --- a/native-modules/react-native-device-utils/package.json +++ b/native-modules/react-native-device-utils/package.json @@ -1,6 +1,6 @@ { "name": "@onekeyfe/react-native-device-utils", - "version": "1.1.35", + "version": "1.1.36", "description": "react-native-device-utils", "main": "./lib/module/index.js", "types": "./lib/typescript/src/index.d.ts", diff --git a/native-modules/react-native-get-random-values/package.json b/native-modules/react-native-get-random-values/package.json index 059a30f..3971bdd 100644 --- a/native-modules/react-native-get-random-values/package.json +++ b/native-modules/react-native-get-random-values/package.json @@ -1,6 +1,6 @@ { "name": "@onekeyfe/react-native-get-random-values", - "version": "1.1.35", + "version": "1.1.36", "description": "react-native-get-random-values", "main": "./lib/module/index.js", "types": "./lib/typescript/src/index.d.ts", diff --git a/native-modules/react-native-keychain-module/package.json b/native-modules/react-native-keychain-module/package.json index dc13c7d..6e95efe 100644 --- a/native-modules/react-native-keychain-module/package.json +++ b/native-modules/react-native-keychain-module/package.json @@ -1,6 +1,6 @@ { "name": "@onekeyfe/react-native-keychain-module", - "version": "1.1.35", + "version": "1.1.36", "description": "react-native-keychain-module", "main": "./lib/module/index.js", "types": "./lib/typescript/src/index.d.ts", diff --git a/native-modules/react-native-lite-card/package.json b/native-modules/react-native-lite-card/package.json index b928662..d873250 100644 --- a/native-modules/react-native-lite-card/package.json +++ b/native-modules/react-native-lite-card/package.json @@ -1,6 +1,6 @@ { "name": "@onekeyfe/react-native-lite-card", - "version": "1.1.35", + "version": "1.1.36", "description": "lite card", "main": "./lib/module/index.js", "types": "./lib/typescript/src/index.d.ts", diff --git a/native-modules/react-native-perf-memory/package.json b/native-modules/react-native-perf-memory/package.json index df8ae5a..725c7ff 100644 --- a/native-modules/react-native-perf-memory/package.json +++ b/native-modules/react-native-perf-memory/package.json @@ -1,6 +1,6 @@ { "name": "@onekeyfe/react-native-perf-memory", - "version": "1.1.35", + "version": "1.1.36", "description": "react-native-perf-memory", "main": "./lib/module/index.js", "types": "./lib/typescript/src/index.d.ts", diff --git a/native-modules/react-native-splash-screen/package.json b/native-modules/react-native-splash-screen/package.json index f6060b0..ab4e3d2 100644 --- a/native-modules/react-native-splash-screen/package.json +++ b/native-modules/react-native-splash-screen/package.json @@ -1,6 +1,6 @@ { "name": "@onekeyfe/react-native-splash-screen", - "version": "1.1.35", + "version": "1.1.36", "description": "react-native-splash-screen", "main": "./lib/module/index.js", "types": "./lib/typescript/src/index.d.ts", diff --git a/native-views/react-native-auto-size-input/package.json b/native-views/react-native-auto-size-input/package.json index 0dfbf8f..bb3565e 100644 --- a/native-views/react-native-auto-size-input/package.json +++ b/native-views/react-native-auto-size-input/package.json @@ -1,6 +1,6 @@ { "name": "@onekeyfe/react-native-auto-size-input", - "version": "1.1.35", + "version": "1.1.36", "description": "Auto-sizing text input with font scaling, prefix and suffix support", "main": "./lib/module/index.js", "types": "./lib/typescript/src/index.d.ts", diff --git a/native-views/react-native-pager-view/package.json b/native-views/react-native-pager-view/package.json index bcf0e7b..bd49fc9 100644 --- a/native-views/react-native-pager-view/package.json +++ b/native-views/react-native-pager-view/package.json @@ -1,6 +1,6 @@ { "name": "@onekeyfe/react-native-pager-view", - "version": "1.1.35", + "version": "1.1.36", "description": "React Native wrapper for Android and iOS ViewPager", "source": "./src/index.tsx", "main": "./lib/module/index.js", diff --git a/native-views/react-native-skeleton/package.json b/native-views/react-native-skeleton/package.json index c03fe7b..b7ce4a6 100644 --- a/native-views/react-native-skeleton/package.json +++ b/native-views/react-native-skeleton/package.json @@ -1,6 +1,6 @@ { "name": "@onekeyfe/react-native-skeleton", - "version": "1.1.35", + "version": "1.1.36", "description": "react-native-skeleton", "main": "./lib/module/index.js", "types": "./lib/typescript/src/index.d.ts", diff --git a/native-views/react-native-tab-view/package.json b/native-views/react-native-tab-view/package.json index 3ca729a..11a8a34 100644 --- a/native-views/react-native-tab-view/package.json +++ b/native-views/react-native-tab-view/package.json @@ -1,6 +1,6 @@ { "name": "@onekeyfe/react-native-tab-view", - "version": "1.1.35", + "version": "1.1.36", "description": "Native Bottom Tabs for React Native (UIKit implementation)", "source": "./src/index.tsx", "main": "./lib/module/index.js", From c5201f9d775c9fbbe99a70610a1c86b209d8a563 Mon Sep 17 00:00:00 2001 From: huhuanming Date: Wed, 11 Mar 2026 00:43:48 +0800 Subject: [PATCH 7/8] Update README.md --- .../react-native-pager-view/README.md | 317 ++---------------- 1 file changed, 21 insertions(+), 296 deletions(-) diff --git a/native-views/react-native-pager-view/README.md b/native-views/react-native-pager-view/README.md index cfbfebf..a5e3262 100644 --- a/native-views/react-native-pager-view/README.md +++ b/native-views/react-native-pager-view/README.md @@ -1,306 +1,31 @@ +# @onekeyfe/react-native-pager-view - - - React Native PagerView - - +First, a sincere thank-you to Callstack and the +`react-native-pager-view` maintainers for their excellent work 🙏 -# react-native-pager-view +This package is built on, and inspired by, +[callstack/react-native-pager-view](https://github.com/callstack/react-native-pager-view). -[![npm package](https://badge.fury.io/js/react-native-pager-view.svg)](https://badge.fury.io/js/react-native-pager-view) -[![Lean Core Extracted](https://img.shields.io/badge/Lean%20Core-Extracted-brightgreen.svg)](https://github.com/facebook/react-native/issues/23313) -[![License](https://img.shields.io/github/license/callstack/react-native-pager-view?color=blue)](https://github.com/callstack/react-native-pager-view/blob/master/LICENSE) +Our original plan was to keep our customizations as patches on top of upstream +`react-native-pager-view`. -[![Lint](https://github.com/callstack/react-native-pager-view/actions/workflows/main.yml/badge.svg)](https://github.com/callstack/react-native-pager-view/actions/workflows/main.yml) -[![iOS Build](https://github.com/callstack/react-native-pager-view/actions/workflows/ios.yml/badge.svg)](https://github.com/callstack/react-native-pager-view/actions/workflows/ios.yml) -[![Android Build](https://github.com/callstack/react-native-pager-view/actions/workflows/android.yml/badge.svg)](https://github.com/callstack/react-native-pager-view/actions/workflows/android.yml) +As our product requirements evolved, the scope of those changes outgrew what we +could reasonably maintain as an upstream patch set. We regret that we were +unable to keep this work in patch form. -This component allows the user to swipe left and right through pages of data. Under the hood it is using the native [Android ViewPager](https://developer.android.com/jetpack/androidx/releases/viewpager2) and the [iOS UIPageViewController](https://developer.apple.com/documentation/uikit/uipageviewcontroller) implementations. [See it in action!](https://github.com/callstack/react-native-pager-view#preview) +As the gap grew, we ultimately forked `react-native-pager-view` to keep +development and delivery stable. -
-

- ViewPager -

+## Upstream Project -
+- Repository: [callstack/react-native-pager-view](https://github.com/callstack/react-native-pager-view) +- License: MIT -## Migration +## Notes -> [!WARNING] -> From version **7.x** only new architecture is supported. +- This fork includes OneKey-specific adaptations for our product requirements. +- If you are looking for original behavior and full documentation, please refer + to the upstream repository. -In version **6.x** support for `transitionStyle` property has been dropped. More information [here](https://github.com/callstack/react-native-pager-view/blob/master/MIGRATION.md). - -`"@react-native-community/viewpager"` library has been changed to `react-native-pager-view`. Here you can find more information, how to migrate pager view to the latest [version](https://github.com/callstack/react-native-pager-view/blob/master/MIGRATION.md) - -## Getting started -Bun: - -`bun add react-native-pager-view` - -Yarn: - - `yarn add react-native-pager-view` - -## Linking - -### >= 0.60 - -Autolinking will just do the job. - -### < 0.60 - -#### Mostly automatic - -`react-native link react-native-pager-view` - -#### Manual linking - -
-Manually link the library on iOS -
- -Follow the [instructions in the React Native documentation](https://facebook.github.io/react-native/img/linking-libraries-ios#manual-linking) to manually link the framework or link using [Cocoapods](https://cocoapods.org) by adding this to your `Podfile`: - -```ruby -pod 'react-native-pager-view', :path => '../node_modules/react-native-pager-view' -``` - -
- -
-Manually link the library on Android -
-Make the following changes: - -#### `android/settings.gradle` - -```groovy -include ':react-native-pager-view' -project(':react-native-pager-view').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-pager-view/android') -``` - -#### `android/app/build.gradle` - -```groovy -dependencies { - ... - implementation project(':react-native-pager-view') -} -``` - -#### `android/app/src/main/.../MainApplication.java` - -On top, where imports are: - -Add `import com.reactnativepagerview.PagerViewPackage;` - -Add the `PagerViewPackage` class to your list of exported packages. - -```java -@Override -protected List getPackages() { - return Arrays.asList( - new MainReactPackage(), - new PagerViewPackage() - ); -} -``` - -
- -## Usage - -```js -import React from 'react'; -import { StyleSheet, View, Text } from 'react-native'; -import PagerView from 'react-native-pager-view'; - -const MyPager = () => { - return ( - - - First page - - - Second page - - - ); -}; - -const styles = StyleSheet.create({ - pagerView: { - flex: 1, - }, -}); -``` - -**Attention:** Note that you can only use `View` components as children of `PagerView` (cf. folder _/example_) -. For Android if `View` has own children, set prop `collapsable` to false , otherwise react-native might remove those children views and and its children will be rendered as separate pages - -## Advanced usage - -For advanced usage please take a look into our [example project](https://github.com/callstack/react-native-pager-view/blob/master/example/src/BasicPagerViewExample.tsx) - -## API - -| Prop | Description | Platform | -| -------------------------------------------------------------------- | :-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------: | -| `initialPage` | Index of initial page that should be selected | both | -| `scrollEnabled: boolean` | Should pager view scroll, when scroll enabled | both | -| `onPageScroll: (e: PageScrollEvent) => void` | Executed when transitioning between pages (either because the animation for the requested page has changed or when the user is swiping/dragging between pages) | both | -| `onPageScrollStateChanged: (e: PageScrollStateChangedEvent) => void` | Function called when the page scrolling state has changed | both | -| `onPageSelected: (e: PageSelectedEvent) => void` | This callback will be called once the ViewPager finishes navigating to the selected page | both | -| `pageMargin: number` | Blank space to be shown between pages | both | -| `keyboardDismissMode: ('none' / 'on-drag')` | Determines whether the keyboard gets dismissed in response to a drag | both | -| `orientation: Orientation` | Set `horizontal` or `vertical` scrolling orientation (it does **not** work dynamically) | both | -| `overScrollMode: OverScrollMode` | Used to override default value of overScroll mode. Can be `auto`, `always` or `never`. Defaults to `auto` | Android | -| `offscreenPageLimit: number` | Set the number of pages that should be retained to either side of the currently visible page(s). Pages beyond this limit will be recreated from the adapter when needed. Defaults to RecyclerView's caching strategy. The given value must either be larger than 0. | Android | -| `overdrag: boolean` | Allows for overscrolling after reaching the end or very beginning or pages. Defaults to `false` | iOS | -| `layoutDirection: ('ltr' / 'rtl' / 'locale')` | Specifies layout direction. Use `ltr` or `rtl` to set explicitly or `locale` to deduce from the default language script of a locale. Defaults to `locale` | both | - -| Method | Description | Platform | -| ------------------------------------------ | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------: | -| `setPage(index: number)` | Function to scroll to a specific page in the PagerView. Invalid index is ignored. | both | -| `setPageWithoutAnimation(index: number)` | Function to scroll to a specific page in the PagerView. Invalid index is ignored. | both | -| `setScrollEnabled(scrollEnabled: boolean)` | A helper function to enable/disable scroll imperatively. The recommended way is using the scrollEnabled prop, however, there might be a case where a imperative solution is more useful (e.g. for not blocking an animation) | both | - -## Contributing - -See the [contributing guide](CONTRIBUTING.md) to learn how to contribute to the repository and the development workflow. - -## Known Issues - -- `flex:1` does not work for child views, please use `width: '100%', height: '100%'` [instead](https://github.com/callstack/react-native-pager-view/issues/186#issuecomment-675320732) - -- [iOS]: In case of `UIViewControllerHierarchyInconsistency` error, please use below fix: - -``` -requestAnimationFrame(() => refPagerView.current?.setPage(index)); -``` - -## Preview - -### Android - -| horizontal | vertical | -| :---------------------------------------------------------------: | :------------------------------------------------------------------------: | -| ViewPager | ViewPager | - -### iOS - -| horizontal | vertical | -| :------------------------------------------------------------------: | :--------------------------------------------------------------------: | -| ViewPager | ViewPager | - -## Reanimated onPageScroll handler - -An example can be found [here](https://github.com/callstack/react-native-pager-view/blob/master/example/src/ReanimatedOnPageScrollExample.tsx) - -#### Instructions - -To attach reanimated handler with `onPageScroll` follow the below steps. - -```jsx -// 1. Define the handler -function usePageScrollHandler(handlers, dependencies) { - const { context, doDependenciesDiffer } = useHandler(handlers, dependencies); - const subscribeForEvents = ['onPageScroll']; - - return useEvent( - (event) => { - 'worklet'; - const { onPageScroll } = handlers; - if (onPageScroll && event.eventName.endsWith('onPageScroll')) { - onPageScroll(event, context); - } - }, - subscribeForEvents, - doDependenciesDiffer - ); -} - -// 2. Attach the event handler -import PagerView from 'react-native-pager-view'; -import Animated from 'react-native-reanimated'; -const AnimatedPagerView = Animated.createAnimatedComponent(PagerView); - -const pageScrollHandler = usePageScrollHandler({ - onPageScroll: (e) => { - 'worklet'; - offset.value = e.offset; - console.log(e.offset, e.position); - }, -}); - -; -``` - -## usePagerView Hook Usage -The `usePagerView` hook is a convenient way to manage the state and control the behavior of the `` component. It provides functions and variables to interact with the pager, such as navigating between pages and enabling/disabling scrolling. - -Below is an example of how to use the usePager hook: - -```jsx -export function PagerHookExample() { - const { AnimatedPagerView, ref, ...rest } = usePagerView({ pagesAmount: 10 }); - - return ( - - - {useMemo( - () => - rest.pages.map((_, index) => ( - - - - {`page number ${index}`} - - - )), - [rest.pages] - )} - - - - ); -} -``` -### How the Example Works: - -- **Pager View Setup**: The `AnimatedPagerView` component wraps `PagerView` in React Native's animation capabilities. It accepts multiple props from the `usePager` hook, such as `overdragEnabled`, `scrollEnabled`, `onPageScroll`, `onPageSelected`, and others to manage pager behavior. - -- **Rendering Pages**: The pages are dynamically generated using the `rest.pages` array (initialized by `usePager`). The `useMemo` hook ensures the pages are only recomputed when necessary for performance reasons. - -### Conclusion - -The `usePager` hook makes it easy to handle pagination with dynamic views. This example demonstrates how to set up a simple paginated interface where users can scroll through pages, interact with page elements, and control the pager with external navigation. - - -## License - -MIT +Thank you again to Callstack and everyone who contributes to +`react-native-pager-view` 💙 From ba29977c00fed422cac817845335cb49ae6879bb Mon Sep 17 00:00:00 2001 From: huhuanming Date: Wed, 11 Mar 2026 00:47:41 +0800 Subject: [PATCH 8/8] docs(changelog): add 1.1.35 and 1.1.36 notes --- CHANGELOG.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c3c1202..79dc3d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,31 @@ All notable changes to this project will be documented in this file. +## [1.1.36] - 2026-03-10 + +### Features +- **auto-size-input**: Add `contentCentered` prop to center prefix/input/suffix as one visual group in single-line mode + +### Bug Fixes +- **auto-size-input (Android)**: Improve baseline centering and centered-layout width calculations for single-line input +- **auto-size-input (iOS)**: Align auto-width sizing behavior with Android so content width and suffix positioning stay consistent + +### Chores +- Bump all native modules and views to 1.1.36 + +## [1.1.35] - 2026-03-10 + +### Features +- **pager-view**: Add local `@onekeyfe/react-native-pager-view` package in `native-views` with iOS and Android support +- **example**: Add PagerView test page and route integration, including nested pager demos + +### Bug Fixes +- **pager-view**: Fix layout metrics/child-view guards and scope refresh-layout callback to the host instance +- **tab-view**: Guard invalid route keys in tab press/long-press callbacks and safely resync selected tab on Android + +### Chores +- Bump all native modules and views to 1.1.35 + ## [1.1.34] - 2026-03-09 ### Bug Fixes