@onekeyfe/react-native-auto-size-input 3.0.130 → 3.0.132
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.
|
@@ -337,6 +337,10 @@ class HybridAutoSizeInput(val context: ThemedReactContext) : HybridAutoSizeInput
|
|
|
337
337
|
if (isDisposed) return
|
|
338
338
|
field = value
|
|
339
339
|
view.requestLayout()
|
|
340
|
+
// requestLayout() is a no-op under React Native (the parent
|
|
341
|
+
// ReactViewGroup swallows it), so a gap change that arrives after the
|
|
342
|
+
// prefix/suffix props has to re-run the fit, which lays the row out again.
|
|
343
|
+
recalculateFontSize()
|
|
340
344
|
}
|
|
341
345
|
|
|
342
346
|
override var suffixMarginLeft: Double? = null
|
|
@@ -345,6 +349,10 @@ class HybridAutoSizeInput(val context: ThemedReactContext) : HybridAutoSizeInput
|
|
|
345
349
|
if (isDisposed) return
|
|
346
350
|
field = value
|
|
347
351
|
view.requestLayout()
|
|
352
|
+
// requestLayout() is a no-op under React Native (the parent
|
|
353
|
+
// ReactViewGroup swallows it), so a gap change that arrives after the
|
|
354
|
+
// prefix/suffix props has to re-run the fit, which lays the row out again.
|
|
355
|
+
recalculateFontSize()
|
|
348
356
|
}
|
|
349
357
|
|
|
350
358
|
override var showBorder: Boolean? = null
|
|
@@ -595,31 +603,43 @@ class HybridAutoSizeInput(val context: ThemedReactContext) : HybridAutoSizeInput
|
|
|
595
603
|
// In contentAutoWidth mode, prioritize width expansion instead of shrinking text.
|
|
596
604
|
if (isContentAutoWidthEnabled) {
|
|
597
605
|
val density = context.resources.displayMetrics.density
|
|
606
|
+
val scaledDensity = context.resources.displayMetrics.scaledDensity
|
|
598
607
|
val edgeInset = (2f * density).toInt()
|
|
599
608
|
val singleLineWidthPadding = contentAutoWidthPaddingPx()
|
|
600
|
-
val
|
|
601
|
-
|
|
609
|
+
val minInputWidth = (24f * density).toInt()
|
|
610
|
+
// Measure prefix/suffix at every candidate size instead of
|
|
611
|
+
// once at the current size. applyFontSize scales them together with the
|
|
612
|
+
// input, so a fit that used their old (smaller) width left performLayout
|
|
613
|
+
// with a slot narrower than the text and the EditText scrolled the
|
|
614
|
+
// leading digits out of view (send-amount fiat toggle: "$" prefix appears
|
|
615
|
+
// while the token suffix disappears and the font grows).
|
|
616
|
+
val prefixText = if (prefixView.visibility == View.VISIBLE) prefixView.text?.toString().orEmpty() else ""
|
|
617
|
+
val suffixText = if (suffixView.visibility == View.VISIBLE) suffixView.text?.toString().orEmpty() else ""
|
|
602
618
|
val prefixGap = if (prefixView.visibility == View.VISIBLE) ((prefixMarginRight ?: 0.0) * density).toInt() else 0
|
|
603
619
|
val suffixGap = if (suffixView.visibility == View.VISIBLE) ((suffixMarginLeft ?: 0.0) * density).toInt() else 0
|
|
604
|
-
val prefixSegment = prefixW + prefixGap
|
|
605
|
-
val suffixSegment = if (suffixView.visibility == View.VISIBLE) suffixGap + suffixW else 0
|
|
606
620
|
val availableTrackWidth = maxOf(width - (edgeInset * 2), 0)
|
|
607
|
-
val
|
|
608
|
-
val maxTextWidth = maxOf(maxInputWidth - singleLineWidthPadding, 0)
|
|
621
|
+
val availableGroupWidth = maxOf(availableTrackWidth - prefixGap - suffixGap, 0)
|
|
609
622
|
val maxTextHeight = maxOf(height - inputView.paddingTop - inputView.paddingBottom, 0)
|
|
610
623
|
val textForSizing = if (inputText.isEmpty()) (placeholder ?: "") else inputText
|
|
611
624
|
val probeText = if (textForSizing.isEmpty()) "0" else textForSizing
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
625
|
+
val typeface = makeTypeface()
|
|
626
|
+
|
|
627
|
+
// Keep both width and line-height within the input slot. The width probe
|
|
628
|
+
// mirrors performLayout: input slot (text + padding, at least the minimum
|
|
629
|
+
// width) plus both side labels, all at the candidate size.
|
|
630
|
+
val widthFitSize = AutoWidthFontFit.findLargestFittingSize(
|
|
631
|
+
minSize = minSize,
|
|
632
|
+
maxSize = maxSize,
|
|
633
|
+
availableWidth = availableGroupWidth.toFloat()
|
|
634
|
+
) { candidateSize ->
|
|
635
|
+
val textSizePx = candidateSize * scaledDensity
|
|
636
|
+
val inputW = maxOf(
|
|
637
|
+
measureSingleLineTextWidthPx(probeText, textSizePx, typeface) + singleLineWidthPadding,
|
|
638
|
+
minInputWidth
|
|
622
639
|
)
|
|
640
|
+
(inputW +
|
|
641
|
+
measureSideLabelWidthPx(prefixText, textSizePx, typeface) +
|
|
642
|
+
measureSideLabelWidthPx(suffixText, textSizePx, typeface)).toFloat()
|
|
623
643
|
}
|
|
624
644
|
val heightFitSize = if (maxTextHeight <= 0) {
|
|
625
645
|
minSize
|
|
@@ -838,30 +858,51 @@ class HybridAutoSizeInput(val context: ThemedReactContext) : HybridAutoSizeInput
|
|
|
838
858
|
}
|
|
839
859
|
|
|
840
860
|
private fun measureSingleLineTextWidthPx(text: String): Int {
|
|
861
|
+
return measureSingleLineTextWidthPx(
|
|
862
|
+
text,
|
|
863
|
+
currentFontSize * context.resources.displayMetrics.scaledDensity,
|
|
864
|
+
makeTypeface()
|
|
865
|
+
)
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
// Size-parameterized so the auto-width fit can probe candidates.
|
|
869
|
+
private fun measureSingleLineTextWidthPx(text: String, textSizePx: Float, typeface: Typeface): Int {
|
|
841
870
|
if (text.isEmpty()) return 0
|
|
842
871
|
val paint = TextPaint(Paint.ANTI_ALIAS_FLAG)
|
|
843
|
-
paint.textSize =
|
|
844
|
-
paint.typeface =
|
|
872
|
+
paint.textSize = textSizePx
|
|
873
|
+
paint.typeface = typeface
|
|
845
874
|
return kotlin.math.ceil(paint.measureText(text).toDouble()).toInt()
|
|
846
875
|
}
|
|
847
876
|
|
|
877
|
+
// Width a prefix/suffix label occupies at [textSizePx]. Shared by
|
|
878
|
+
// the auto-width fit and measureTextViewWidthPx so both agree per size.
|
|
879
|
+
private fun measureSideLabelWidthPx(text: String, textSizePx: Float, typeface: Typeface): Int {
|
|
880
|
+
if (text.isEmpty()) return 0
|
|
881
|
+
val paint = TextPaint(Paint.ANTI_ALIAS_FLAG)
|
|
882
|
+
paint.textSize = textSizePx
|
|
883
|
+
paint.typeface = typeface
|
|
884
|
+
val bounds = Rect()
|
|
885
|
+
paint.getTextBounds(text, 0, text.length, bounds)
|
|
886
|
+
val advanceWidth = kotlin.math.ceil(paint.measureText(text).toDouble()).toInt()
|
|
887
|
+
return maxOf(advanceWidth, bounds.width()) + sideLabelSafetyPaddingPx(textSizePx)
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
// Keep extra room for side bearings/kerning to avoid clipping on some glyphs/fonts.
|
|
891
|
+
private fun sideLabelSafetyPaddingPx(textSizePx: Float): Int {
|
|
892
|
+
val density = context.resources.displayMetrics.density
|
|
893
|
+
return maxOf((4f * density).toInt(), kotlin.math.ceil(textSizePx * 0.5f).toInt())
|
|
894
|
+
}
|
|
895
|
+
|
|
848
896
|
private fun measureTextViewWidthPx(textView: TextView): Int {
|
|
849
897
|
val content = textView.text?.toString().orEmpty()
|
|
850
898
|
if (content.isEmpty()) return 0
|
|
851
|
-
val bounds = Rect()
|
|
852
|
-
textView.paint.getTextBounds(content, 0, content.length, bounds)
|
|
853
|
-
val advanceWidth = kotlin.math.ceil(textView.paint.measureText(content).toDouble()).toInt()
|
|
854
|
-
val glyphWidth = bounds.width()
|
|
855
899
|
textView.measure(
|
|
856
900
|
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
|
|
857
901
|
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
|
|
858
902
|
)
|
|
859
|
-
val desiredWidth = textView.measuredWidth
|
|
860
|
-
val
|
|
861
|
-
|
|
862
|
-
val safetyPadding = maxOf((4f * density).toInt(), kotlin.math.ceil(textView.textSize * 0.5f).toInt())
|
|
863
|
-
val measured = maxOf(maxOf(advanceWidth, glyphWidth), desiredWidth) + safetyPadding
|
|
864
|
-
return measured
|
|
903
|
+
val desiredWidth = textView.measuredWidth + sideLabelSafetyPaddingPx(textView.textSize)
|
|
904
|
+
val paintWidth = measureSideLabelWidthPx(content, textView.textSize, textView.typeface)
|
|
905
|
+
return maxOf(paintWidth, desiredWidth)
|
|
865
906
|
}
|
|
866
907
|
|
|
867
908
|
private fun requestInputFocus() {
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
package com.margelo.nitro.autosizeinput
|
|
2
|
+
|
|
3
|
+
// Font fit for contentAutoWidth mode. The input text, prefix,
|
|
4
|
+
// suffix and their safety padding all scale with the font, so every candidate
|
|
5
|
+
// size must be measured as a whole. Measuring the side labels once at the
|
|
6
|
+
// previous font size under-reports their width whenever the font grows (e.g.
|
|
7
|
+
// the send-amount fiat toggle adds a "$" prefix and drops the token suffix) and
|
|
8
|
+
// the layout pass then hands the input a slot narrower than its text, which
|
|
9
|
+
// scrolls the leading digits out of view.
|
|
10
|
+
internal object AutoWidthFontFit {
|
|
11
|
+
fun findLargestFittingSize(
|
|
12
|
+
minSize: Float,
|
|
13
|
+
maxSize: Float,
|
|
14
|
+
availableWidth: Float,
|
|
15
|
+
measureAt: (Float) -> Float
|
|
16
|
+
): Float {
|
|
17
|
+
if (availableWidth <= 0f || maxSize <= minSize) return minSize
|
|
18
|
+
var low = minSize
|
|
19
|
+
var high = maxSize
|
|
20
|
+
while (high - low > 0.5f) {
|
|
21
|
+
val mid = (low + high) / 2f
|
|
22
|
+
if (measureAt(mid) <= availableWidth) {
|
|
23
|
+
low = mid
|
|
24
|
+
} else {
|
|
25
|
+
high = mid
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return low
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
package com.margelo.nitro.autosizeinput
|
|
2
|
+
|
|
3
|
+
import org.junit.Assert.assertEquals
|
|
4
|
+
import org.junit.Assert.assertTrue
|
|
5
|
+
import org.junit.Test
|
|
6
|
+
|
|
7
|
+
class AutoWidthFontFitTest {
|
|
8
|
+
// Synthetic metrics: ten digits plus a "$" prefix whose safety padding
|
|
9
|
+
// scales with the font, mirroring measureTextViewWidthPx.
|
|
10
|
+
private fun contentWidthAt(size: Float): Float {
|
|
11
|
+
val text = 10 * 0.58f * size
|
|
12
|
+
val prefix = 0.55f * size + 0.5f * size
|
|
13
|
+
return text + prefix
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
@Test
|
|
17
|
+
fun scalesSideLabelsWithTheCandidateSize() {
|
|
18
|
+
val available = 400f
|
|
19
|
+
val size = AutoWidthFontFit.findLargestFittingSize(16f, 67f, available, ::contentWidthAt)
|
|
20
|
+
|
|
21
|
+
assertTrue(contentWidthAt(size) <= available)
|
|
22
|
+
assertTrue(contentWidthAt(size + 0.5f) > available)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
@Test
|
|
26
|
+
fun regressionPrefixMeasuredAtOldSizeOverflows() {
|
|
27
|
+
// Before the fix the prefix was measured once at the previous font size
|
|
28
|
+
// (20sp here) and only the text was fitted, so the chosen size overflowed
|
|
29
|
+
// once applyFontSize scaled the prefix up as well.
|
|
30
|
+
val available = 400f
|
|
31
|
+
val stalePrefix = 0.55f * 20f + 0.5f * 20f
|
|
32
|
+
val staleFit = AutoWidthFontFit.findLargestFittingSize(16f, 67f, available - stalePrefix) { 10 * 0.58f * it }
|
|
33
|
+
assertTrue(contentWidthAt(staleFit) > available)
|
|
34
|
+
|
|
35
|
+
val fit = AutoWidthFontFit.findLargestFittingSize(16f, 67f, available, ::contentWidthAt)
|
|
36
|
+
assertTrue(contentWidthAt(fit) <= available)
|
|
37
|
+
assertTrue(fit < staleFit)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
@Test
|
|
41
|
+
fun returnsMinSizeWhenNothingFits() {
|
|
42
|
+
assertEquals(16f, AutoWidthFontFit.findLargestFittingSize(16f, 67f, 0f, ::contentWidthAt), 0f)
|
|
43
|
+
assertEquals(16f, AutoWidthFontFit.findLargestFittingSize(16f, 67f, 10f, ::contentWidthAt), 0f)
|
|
44
|
+
}
|
|
45
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onekeyfe/react-native-auto-size-input",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.132",
|
|
4
4
|
"description": "Auto-sizing text input with font scaling, prefix and suffix support",
|
|
5
5
|
"main": "./lib/module/index.js",
|
|
6
6
|
"types": "./lib/typescript/src/index.d.ts",
|