Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 81 additions & 1 deletion example/__tests__/nitro-input-sizing.harness.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
import React, { createRef, useLayoutEffect, useRef, useState } from 'react'
import { View } from 'react-native'
import { describe, expect, it } from 'react-native-harness'
import { NitroInput, NumberFormat, type NitroInputHandle, type NitroInputProps } from 'react-native-nitro-input'
import { NitroInput, NitroNumber, NitroText, NumberFormat, type NitroInputHandle, type NitroInputProps } from 'react-native-nitro-input'
import { render, sleep, waitFor } from './test-utils'

const usd = new NumberFormat('en-US', { style: 'currency', currency: 'USD' })
Expand Down Expand Up @@ -136,3 +136,83 @@ describe('NitroInput first layout', () => {
await waitFor(() => expect(rectOf(state.view!).width).toBeGreaterThan(before + 20))
})
})

describe('a TextInput style', () => {
// Fabric hands a view its style's keys as props: when a native prop shared a
// name with a text style key, `style={{ fontWeight: '700' }}` threw. The
// native props are named so that none can (PropNameCollisions.test); this is
// the same check on a device, for every component, every text style key.
const textStyle = {
fontSize: 22,
fontWeight: '700' as const,
fontFamily: 'OpenRunde-Semibold',
fontStyle: 'italic' as const,
color: 'tomato',
letterSpacing: 1,
lineHeight: 30,
textAlign: 'center' as const,
textAlignVertical: 'center' as const,
textDecorationLine: 'underline' as const,
textTransform: 'uppercase' as const,
fontVariant: ['tabular-nums' as const],
}

it('is taken by NitroInput, as TextInput takes it', async () => {
const { state, Box } = boxed()
await render(
<Box>
<NitroInput defaultValue="styled" autoWidth style={textStyle} />
</Box>
)
await expectFinalAtMount(state)
// Measured at the style's 22 pt, not the default 32.
expect(state.mounted!.height).toBeLessThan(40)
})

it('does not reach NitroNumber or NitroText as props', async () => {
const { state, Box } = boxed()
await render(
<Box>
{/* Their style is a view style; a text style is what an app might still pass. */}
<NitroNumber value={1234.5} style={textStyle as never} />
<NitroText style={textStyle as never}>styled</NitroText>
</Box>
)
await waitFor(() => expect(state.mounted).not.toBeNull())
await sleep(SETTLE_MS)
expect(rectOf(state.view!).width).toBeGreaterThan(0)
})
})

describe('a size TextInput would give', () => {
it('is lineHeight tall on one line', async () => {
const { state, Box } = boxed()
await render(
<Box>
<NitroInput defaultValue="Tall" lineHeight={40} fontSize={18} autoWidth />
</Box>
)
await expectFinalAtMount(state)
expect(state.mounted!.height).toBeCloseTo(40, 0)
})

it('grows by its padding and border instead of squeezing the text', async () => {
const plain = boxed()
const padded = boxed()
await render(
<View>
<plain.Box>
<NitroInput defaultValue="blue" fontSize={18} autoWidth />
</plain.Box>
<padded.Box>
<NitroInput defaultValue="blue" fontSize={18} autoWidth style={{ paddingHorizontal: 8, paddingVertical: 3, borderWidth: 1 }} />
</padded.Box>
</View>
)
await expectFinalAtMount(padded.state)
const a = rectOf(plain.state.view!)
const b = rectOf(padded.state.view!)
expect(b.width - a.width).toBeCloseTo(18, 0)
expect(b.height - a.height).toBeCloseTo(8, 0)
})
})
27 changes: 27 additions & 0 deletions packages/react-native-nitro-input/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,33 @@
text area as the field's own text and hint are: in a padded field narrower
than its placeholder (or once the typed text filled it), the tail ran over
the padding to the field's edge. iOS already clipped it.
- **A `TextInput` style no longer crashes a field, and `NitroInput` reads
its typography from it.** Fabric hands a view its style's keys as props,
and six to eight of each view's native props shared a name with a text
style key: `style={{ fontWeight: '700' }}` threw ("Value is a string,
expected a number") on `NitroInput`, `NitroNumber` and `NitroText`, and a
numeric `fontSize` or `color` there was fought over by the style and the
prop. The native props are renamed (`textSize`, `textWeight`,
`textFamily`, `textColor`, `textLetterSpacing`, `textLineHeight`,
`textAlignment`, `textVerticalAlignment`); the components' own props keep
their names. A test now fails if any native prop shares a name with a React
Native style key or view prop. `NitroInput` also takes `fontSize`,
`fontWeight`, `fontFamily`, `color`, `letterSpacing`, `lineHeight`,
`textAlign` and `textAlignVertical` from `style`, as `TextInput` does (its
`style` is typed as `TextInput`'s); the props of the same name win.
- **`NitroInput` matches `TextInput` in three more places**, found by a
visual comparison of the two side by side:
- Android: `textAlign` `center` / `right` on a plain field did nothing; the
alignment set when the field became plain (before `textAlign` arrived in
the same commit) overrode it.
- A `style` padding or border insets the text and adds to the size the
field works out for itself, as `TextInput`'s does. An `autoWidth` field
with `paddingHorizontal: 8` squeezed its text to "…" on iOS; on Android
the padding did not inset the text at all (React Native never hands a
Nitro view its padding there, so the view now insets its text, overlay and
frame itself).
- iOS: `lineHeight` sets a single line's height, as on Android; it was the
font's own line box whatever the prop said.

## 0.3.8

Expand Down
2 changes: 1 addition & 1 deletion packages/react-native-nitro-input/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -587,7 +587,7 @@ screen behaves like theirs with these settings:
| `onKeyPress` | `(event) => void` | – | Before the text changes: the character, `'Backspace'` or `'Enter'`. |
| `onNativeRef` | `(ref) => void` | – | Receives the Nitro object on mount. |
| `id` / `aria-label` | `string` | – | React Native's HTML-style aliases for `nativeID` / `accessibilityLabel`, resolved here because a Nitro view is handed the raw props; each wins over the older spelling. |
| `style`, `testID`, … | `ViewProps` | – | Regular view props. `testID` and the accessibility label are forwarded to the system field, the element VoiceOver, TalkBack and e2e tools interact with. The field takes its width from its parent like a `TextInput`; see `autoWidth`. |
| `style`, `testID`, … | `ViewProps`; `style` as `TextInput`'s | – | Regular view props. `testID` and the accessibility label are forwarded to the system field, the element VoiceOver, TalkBack and e2e tools interact with. The field takes its width from its parent like a `TextInput`; see `autoWidth`. As on a `TextInput`, `style` can carry the typography (`fontSize`, `fontWeight`, `fontFamily`, `color`, `letterSpacing`, `lineHeight`, `textAlign`, `textAlignVertical`; the props of the same name win), and its padding and border inset the text and add to a size the field works out for itself. Two deliberate differences: digits are always tabular (Android's Roboto has them anyway, and a field keeps its width whether it reflows or not), and Android gets no default padding (iOS's `TextInput` has none; React Native's Android one keeps the platform field's). |

`keyboardType`: `'default' | 'number-pad' | 'decimal-pad' | 'numeric' | 'email-address' | 'phone-pad' | 'url' | 'ascii-capable' | 'numbers-and-punctuation'`.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ class HybridNitroInputView(private val context: ThemedReactContext) : HybridNitr
set(v) { field = v; markConfigDirty() }
override var suffixAlign: NitroInputAffixAlign = NitroInputAffixAlign.BASELINE
set(v) { field = v; markConfigDirty() }
override var letterSpacing: Double = 0.0
override var textLetterSpacing: Double = 0.0
set(v) { field = v; markConfigDirty() }
override var prefixSpacing: Double = Double.POSITIVE_INFINITY
set(v) { field = v; markConfigDirty() }
Expand Down Expand Up @@ -171,17 +171,17 @@ class HybridNitroInputView(private val context: ThemedReactContext) : HybridNitr
set(v) { field = v; markConfigDirty() }
override var effect: NitroInputEffect = NitroInputEffect.AUTO
set(v) { field = v; markConfigDirty() }
override var fontSize: Double = 32.0
override var textSize: Double = 32.0
set(v) { field = v; markConfigDirty() }
override var lineHeight: Double = 0.0
override var textLineHeight: Double = 0.0
set(v) { field = v; markConfigDirty() }
override var fontWeight: Double = 400.0
override var textWeight: Double = 400.0
set(v) { field = v; markConfigDirty() }
override var fontFamily: String = ""
override var textFamily: String = ""
set(v) { field = v; markConfigDirty() }
override var color: Double = Double.NaN
override var textColor: Double = Double.NaN
set(v) { field = v; markConfigDirty() }
override var textAlign: NitroInputTextAlign = NitroInputTextAlign.AUTO
override var textAlignment: NitroInputTextAlign = NitroInputTextAlign.AUTO
set(v) { field = v; markConfigDirty() }
override var rightToLeft: Boolean = false
set(v) { field = v; markConfigDirty() }
Expand Down Expand Up @@ -213,7 +213,15 @@ class HybridNitroInputView(private val context: ThemedReactContext) : HybridNitr
set(v) { field = v; markConfigDirty() }
override var numberOfLines: Double = 0.0
set(v) { field = v; markConfigDirty() }
override var textAlignVertical: NitroInputTextAlignVertical = NitroInputTextAlignVertical.AUTO
override var textVerticalAlignment: NitroInputTextAlignVertical = NitroInputTextAlignVertical.AUTO
set(v) { field = v; markConfigDirty() }
override var textInsetLeft: Double = 0.0
set(v) { field = v; markConfigDirty() }
override var textInsetTop: Double = 0.0
set(v) { field = v; markConfigDirty() }
override var textInsetRight: Double = 0.0
set(v) { field = v; markConfigDirty() }
override var textInsetBottom: Double = 0.0
set(v) { field = v; markConfigDirty() }
override var scrollEnabled: Boolean = true
set(v) { field = v; markConfigDirty() }
Expand Down Expand Up @@ -414,7 +422,7 @@ class HybridNitroInputView(private val context: ThemedReactContext) : HybridNitr
prefixFontSize = Double.NaN
suffixFontSize = Double.NaN
signPlacement = NitroInputSignPlacement.BEFOREAFFIX
letterSpacing = 0.0
textLetterSpacing = 0.0
prefixSpacing = Double.POSITIVE_INFINITY
suffixSpacing = Double.POSITIVE_INFINITY
prefixOffset = 0.0
Expand All @@ -438,12 +446,12 @@ class HybridNitroInputView(private val context: ThemedReactContext) : HybridNitr
easing = NitroInputEasing.EXPO
bounce = 0.15
effect = NitroInputEffect.AUTO
fontSize = 32.0
lineHeight = 0.0
fontWeight = 400.0
fontFamily = ""
color = Double.NaN
textAlign = NitroInputTextAlign.AUTO
textSize = 32.0
textLineHeight = 0.0
textWeight = 400.0
textFamily = ""
textColor = Double.NaN
textAlignment = NitroInputTextAlign.AUTO
rightToLeft = false
caretColor = Double.NaN
selectionColor = Double.NaN
Expand All @@ -459,7 +467,11 @@ class HybridNitroInputView(private val context: ThemedReactContext) : HybridNitr
editable = true
multiline = false
numberOfLines = 0.0
textAlignVertical = NitroInputTextAlignVertical.AUTO
textVerticalAlignment = NitroInputTextAlignVertical.AUTO
textInsetLeft = 0.0
textInsetTop = 0.0
textInsetRight = 0.0
textInsetBottom = 0.0
scrollEnabled = true
autoFocus = false
plain = false
Expand Down Expand Up @@ -631,7 +643,7 @@ class HybridNitroInputView(private val context: ThemedReactContext) : HybridNitr
// which sizes the field before this view exists.
inputView.format = formatOf(this)
inputView.typography = sizingTypographyOf(this, inputView.resources.displayMetrics.density).copy(
color = colorFromARGB(color),
color = colorFromARGB(textColor),
placeholderColor = colorFromARGB(placeholderColor),
prefixAlign = mapAffixAlign(prefixAlign),
suffixAlign = mapAffixAlign(suffixAlign),
Expand Down Expand Up @@ -699,7 +711,7 @@ class HybridNitroInputView(private val context: ThemedReactContext) : HybridNitr
plain = plain,
multiline = multiline,
numberOfLines = clampInt(numberOfLines, 0, 1000, 0),
textAlignVertical = when (textAlignVertical) {
textAlignVertical = when (textVerticalAlignment) {
NitroInputTextAlignVertical.TOP -> NitroInputView.TextAlignVertical.TOP
NitroInputTextAlignVertical.CENTER -> NitroInputView.TextAlignVertical.CENTER
NitroInputTextAlignVertical.BOTTOM -> NitroInputView.TextAlignVertical.BOTTOM
Expand Down Expand Up @@ -735,7 +747,11 @@ class HybridNitroInputView(private val context: ThemedReactContext) : HybridNitr
strokeWidth = Math.max(0.0, finite(strokeWidth, 1.0)).toFloat(),
fillColor = colorFromARGB(fillColor),
)
inputView.alignment = when (textAlign) {
inputView.contentInsets = android.graphics.RectF(
Math.max(0.0, finite(textInsetLeft, 0.0)).toFloat(), Math.max(0.0, finite(textInsetTop, 0.0)).toFloat(),
Math.max(0.0, finite(textInsetRight, 0.0)).toFloat(), Math.max(0.0, finite(textInsetBottom, 0.0)).toFloat(),
)
inputView.alignment = when (textAlignment) {
NitroInputTextAlign.CENTER -> NitroInputView.Alignment.CENTER
NitroInputTextAlign.RIGHT -> NitroInputView.Alignment.RIGHT
NitroInputTextAlign.LEFT -> NitroInputView.Alignment.LEFT
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ class HybridNitroNumberView(private val context: ThemedReactContext) : HybridNit
set(v) { field = v; markConfigDirty() }
override var shimmerDelay: Double? = null
set(v) { field = v; markConfigDirty() }
override var fontSize: Double? = null
override var textSize: Double? = null
set(v) { field = v; markConfigDirty() }
override var prefixFontSize: Double? = null
set(v) { field = v; markConfigDirty() }
Expand All @@ -153,7 +153,7 @@ class HybridNitroNumberView(private val context: ThemedReactContext) : HybridNit
set(v) { field = v; markConfigDirty() }
override var suffixAlign: NitroNumberAffixAlign? = null
set(v) { field = v; markConfigDirty() }
override var letterSpacing: Double? = null
override var textLetterSpacing: Double? = null
set(v) { field = v; markConfigDirty() }
override var prefixSpacing: Double? = null
set(v) { field = v; markConfigDirty() }
Expand Down Expand Up @@ -201,13 +201,13 @@ class HybridNitroNumberView(private val context: ThemedReactContext) : HybridNit
set(v) { field = v; markConfigDirty() }
override var maxFontSizeMultiplier: Double? = null
set(v) { field = v; markConfigDirty() }
override var fontWeight: Double? = null
override var textWeight: Double? = null
set(v) { field = v; markConfigDirty() }
override var fontFamily: String? = null
override var textFamily: String? = null
set(v) { field = v; markConfigDirty() }
override var color: Double? = null
override var textColor: Double? = null
set(v) { field = v; markConfigDirty() }
override var textAlign: NitroNumberTextAlign? = null
override var textAlignment: NitroNumberTextAlign? = null
set(v) { field = v; markConfigDirty() }
override var rightToLeft: Boolean? = null
set(v) { field = v; markConfigDirty() }
Expand Down Expand Up @@ -360,13 +360,13 @@ class HybridNitroNumberView(private val context: ThemedReactContext) : HybridNit
shimmerBaseColor = null
shimmerDirection = null
shimmerDelay = null
fontSize = null
textSize = null
prefixFontSize = null
suffixFontSize = null
affixAlign = null
prefixAlign = null
suffixAlign = null
letterSpacing = null
textLetterSpacing = null
prefixSpacing = null
suffixSpacing = null
prefixOffset = null
Expand All @@ -391,10 +391,10 @@ class HybridNitroNumberView(private val context: ThemedReactContext) : HybridNit
minimumFontScale = null
allowFontScaling = null
maxFontSizeMultiplier = null
fontWeight = null
fontFamily = null
color = null
textAlign = null
textWeight = null
textFamily = null
textColor = null
textAlignment = null
rightToLeft = null
onSizeChange = null
configDirty = true
Expand Down Expand Up @@ -447,15 +447,15 @@ class HybridNitroNumberView(private val context: ThemedReactContext) : HybridNit
configDirty = false

rollingView.typography = NitroNumberView.Typography(
fontSize = (fontSize ?: 32.0).toFloat(),
fontSize = (textSize ?: 32.0).toFloat(),
prefixFontSize = prefixFontSize?.toFloat(),
suffixFontSize = suffixFontSize?.toFloat(),
fontWeight = clampInt(fontWeight, 100, 900, 400),
fontFamily = fontFamily,
color = color?.let { colorFromARGB(it) },
fontWeight = clampInt(textWeight, 100, 900, 400),
fontFamily = textFamily,
color = textColor?.let { colorFromARGB(it) },
prefixAlign = mapAffixAlign(prefixAlign ?: affixAlign),
suffixAlign = mapAffixAlign(suffixAlign ?: affixAlign),
letterSpacing = (letterSpacing ?: 0.0).takeIf { it.isFinite() }?.toFloat() ?: 0f,
letterSpacing = (textLetterSpacing ?: 0.0).takeIf { it.isFinite() }?.toFloat() ?: 0f,
prefixSpacing = prefixSpacing?.takeIf { it.isFinite() }?.toFloat(),
suffixSpacing = suffixSpacing?.takeIf { it.isFinite() }?.toFloat(),
prefixOffset = (prefixOffset ?: 0.0).takeIf { it.isFinite() }?.toFloat() ?: 0f,
Expand Down Expand Up @@ -545,7 +545,7 @@ class HybridNitroNumberView(private val context: ThemedReactContext) : HybridNit
downColor = flashDownColor?.let { colorFromARGB(it) },
durationMs = Math.max(50.0, flashDuration ?: 600.0).toLong(),
)
rollingView.alignment = when (textAlign) {
rollingView.alignment = when (textAlignment) {
NitroNumberTextAlign.CENTER -> NitroNumberView.Alignment.CENTER
NitroNumberTextAlign.RIGHT -> NitroNumberView.Alignment.RIGHT
NitroNumberTextAlign.LEFT -> NitroNumberView.Alignment.LEFT
Expand Down
Loading
Loading